Compare commits
168 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f2efc685d3 | ||
|
67d10e5f39 | ||
|
770c16a66d | ||
|
c2cc03a472 | ||
|
b02e1e04fc | ||
|
cf8c059711 | ||
|
b343420af6 | ||
|
1ed2b589a2 | ||
|
72d8dc89fb | ||
|
8706aa4a46 | ||
|
57dcba666e | ||
|
489573afb9 | ||
|
a9593bad66 | ||
|
d0d8db7c45 | ||
|
a07150b6dd | ||
|
519f091fe8 | ||
|
9c2689301c | ||
|
a1665ab37a | ||
|
1be0e8776f | ||
|
92082ac927 | ||
|
c622f3a8d6 | ||
|
f1817c9c6b | ||
|
16e97cce9b | ||
|
bc404c9a82 | ||
|
29bb63b717 | ||
|
0d917bbf37 | ||
|
45c05c4e2b | ||
|
77b1a25faf | ||
|
34ed5235a3 | ||
|
5996a91dde | ||
|
7171c00e42 | ||
|
4dc593eca3 | ||
|
f1984650f4 | ||
|
33ae45ad65 | ||
|
e3a2fe52cd | ||
|
5fd7da9de7 | ||
|
edc8a8b771 | ||
|
1ce0df4e63 | ||
|
415fb3a730 | ||
|
172ffee8c3 | ||
|
48c068d88d | ||
|
3f7152a4f5 | ||
|
54f58a15d3 | ||
|
bcb6c12aa1 | ||
|
1cb40831a4 | ||
|
d9fc2c922d | ||
|
212c6c5ae9 | ||
|
1d8047a272 | ||
|
98bb4a69c2 | ||
|
e69413b763 | ||
|
45f18042b7 | ||
|
0672b051cc | ||
|
3c496720cc | ||
|
881cb570d5 | ||
|
c6a2c8de6c | ||
|
71bacf6991 | ||
|
a0b257b572 | ||
|
a082c151f0 | ||
|
302ab42a97 | ||
|
531d4dd24a | ||
|
1c401a852e | ||
|
25e6dcc9b6 | ||
|
4006d9f102 | ||
|
4c821baab4 | ||
|
c8a35afc92 | ||
|
54f67db275 | ||
|
fd04722706 | ||
|
4cee1f19f6 | ||
|
ef8b5e28b0 | ||
|
818f150b25 | ||
|
446d3fc72e | ||
|
240052246f | ||
|
156a51ab10 | ||
|
52a4beb072 | ||
|
395d70cf01 | ||
|
3732dc2f42 | ||
|
9d3cb65daa | ||
|
a0d3917832 | ||
|
9968c7d007 | ||
|
68f5e71153 | ||
|
af328ee7b4 | ||
|
eebaa64d8c | ||
|
88505388c1 | ||
|
8a778644cf | ||
|
d3a76e646a | ||
|
cfa824bc5f | ||
|
39be61685c | ||
|
ac2106ced7 | ||
|
1b4f7d8a68 | ||
|
5eb2e79b86 | ||
|
a2eff9918e | ||
|
52a470532d | ||
|
cd9441fafb | ||
|
356cf82af5 | ||
|
5372707d0e | ||
|
a1deb5c44e | ||
|
956b1c6867 | ||
|
55aca8b0bf | ||
|
ba8582a47a | ||
|
f409468ccd | ||
|
d982225a54 | ||
|
217190c4d6 | ||
|
a56e97b47d | ||
|
b4f47b1cc9 | ||
|
070cebd605 | ||
|
541e894507 | ||
|
c666558f8c | ||
|
6444b7e24c | ||
|
023245a7ba | ||
|
2a2ad553a1 | ||
|
909e13a24a | ||
|
b17a802675 | ||
|
c3c0543733 | ||
|
b39ec4472c | ||
|
b33489e481 | ||
|
8fb5e20a22 | ||
|
0315b4480f | ||
|
ccbc1b9cf3 | ||
|
19fdfba0bf | ||
|
d00ac200dd | ||
|
173f7107e2 | ||
|
d00d76bf7c | ||
|
65068e8b82 | ||
|
c2cfe5310c | ||
|
07068379c6 | ||
|
528b5f58de | ||
|
378af01f77 | ||
|
c317547e4d | ||
|
e55437698b | ||
|
e365cad930 | ||
|
56735b4427 | ||
|
73e22eb5b1 | ||
|
c04b974311 | ||
|
75be57d6e4 | ||
|
270e9118c4 | ||
|
5d3d61855c | ||
|
7e0ee9ec08 | ||
|
2ae4214215 | ||
|
edaa0a0719 | ||
|
44b934d458 | ||
|
65a90f5a21 | ||
|
1eb4398b6c | ||
|
9b99d50396 | ||
|
68ab671bd0 | ||
|
f4cdfaf27f | ||
|
d486125d07 | ||
|
1599d717af | ||
|
a941a4772b | ||
|
dca078f30b | ||
|
cbbf9f7e3b | ||
|
a54dee31de | ||
|
1bd541b69e | ||
|
e769802939 | ||
|
a3741f8a11 | ||
|
6246fa2bcb | ||
|
c9b40cb33b | ||
|
982e6068cf | ||
|
e8b050ffd5 | ||
|
13f8e4fef7 | ||
|
1fe528c411 | ||
|
d0d9582b81 | ||
|
42bdca63da | ||
|
02260dcaa3 | ||
|
eb7788ce25 | ||
|
3b6f38a45c | ||
|
94ea766c9e | ||
|
ff9ad875af | ||
|
d970586a29 |
@@ -1,7 +1,7 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.8.5
|
||||
- 1.9.2
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
notifications:
|
||||
slack:
|
||||
secure: aEvhLbhujaGaKSrOokiG3//PaVHTIrc3fBpoRbCRqfZpyq6WREoapJJhF+tIpWWOwaC9GmChbD6aHo/jMUgwKXVyPSaNjiEL87YzUUpL8B2zslNp1rgfTg/LrzthOx3Q1TYwpaAl3to0fuHUVFX4yMeC2vuThq7WSXgMMxFCtbc=
|
||||
|
371
README.md
371
README.md
@@ -1,10 +1,16 @@
|
||||
# Go Micro [](https://opensource.org/licenses/Apache-2.0) [](https://godoc.org/github.com/micro/go-micro) [](https://travis-ci.org/micro/go-micro) [](https://goreportcard.com/report/github.com/micro/go-micro)
|
||||
|
||||
Go Micro is a pluggable RPC framework for distributed systems development.
|
||||
Go Micro is a pluggable framework for micro service development.
|
||||
|
||||
The **Micro** philosophy is sane defaults with a pluggable architecture. We provide defaults to get you started quickly but everything can be easily swapped out. It comes with built in support for {json,proto}-rpc encoding, consul or multicast dns for service discovery, http for communication and random hashed client side load balancing.
|
||||
## Overview
|
||||
|
||||
Everything in go-micro is **pluggable**. You can find and contribute to plugins at [github.com/micro/go-plugins](https://github.com/micro/go-plugins).
|
||||
Go Micro provides the core requirements for distributed systems development including RPC and Event driven communication.
|
||||
The **micro** philosophy is sane defaults with a pluggable architecture. We provide defaults to get you started quickly
|
||||
but everything can be easily swapped out.
|
||||
|
||||
<img src="https://micro.mu/docs/images/go-micro.png" />
|
||||
|
||||
Plugins are available at [github.com/micro/go-plugins](https://github.com/micro/go-plugins).
|
||||
|
||||
Follow us on [Twitter](https://twitter.com/microhq) or join the [Slack](http://slack.micro.mu/) community.
|
||||
|
||||
@@ -12,351 +18,38 @@ Follow us on [Twitter](https://twitter.com/microhq) or join the [Slack](http://s
|
||||
|
||||
Go Micro abstracts away the details of distributed systems. Here are the main features.
|
||||
|
||||
- **Service Discovery** - Automatic registration and name resolution with service discovery
|
||||
- **Load Balancing** - Smart client side load balancing of services built on discovery
|
||||
- **Synchronous Comms** - RPC based communication with support for bidirectional streaming
|
||||
- **Asynchronous Comms** - PubSub interface built in for event driven architectures
|
||||
- **Message Encoding** - Dynamic encoding based on content-type with protobuf and json out of the box
|
||||
- **Service Interface** - All features are packaged in a simple high level interface for developing microservices
|
||||
- **Service Discovery** - Automatic service registration and name resolution. Service discovery is at the core of micro service
|
||||
development. When service A needs to speak to service B it needs the location of that service. Consul is the default discovery
|
||||
system with multicast DNS (mdns) as a local option or the SWIM protocol (gossip) for zero dependency p2p networks.
|
||||
|
||||
Go Micro supports both the Service and Function programming models. Read on to learn more.
|
||||
- **Load Balancing** - Client side load balancing built on service discovery. Once we have the addresses of any number of instances
|
||||
of a service we now need a way to decide which node to route to. We use random hashed load balancing to provide even distribution
|
||||
across the services and retry a different node if there's a problem.
|
||||
|
||||
## Docs
|
||||
- **Message Encoding** - Dynamic message encoding based on content-type. The client and server will use codecs along with content-type
|
||||
to seamlessly encode and decode Go types for you. Any variety of messages could be encoded and sent from different clients. The client
|
||||
and server handle this by default. This includes proto-rpc and json-rpc by default.
|
||||
|
||||
For more detailed information on the architecture, installation and use of go-micro checkout the [docs](https://micro.mu/docs).
|
||||
- **Sync Streaming** - RPC based request/response with support for bidirectional streaming. We provide an abstraction for synchronous
|
||||
communication. A request made to a service will be automatically resolved, load balanced, dialled and streamed. The default
|
||||
transport is http/1.1 or http2 when tls is enabled.
|
||||
|
||||
## Learn By Example
|
||||
- **Async Messaging** - PubSub is built in as a first class citizen for asynchronous communication and event driven architectures.
|
||||
Event notifications are a core pattern in micro service development. The default messaging is point-to-point http/1.1 or http2 when tls
|
||||
is enabled.
|
||||
|
||||
An example service can be found in [**examples/service**](https://github.com/micro/examples/tree/master/service) and function in [**examples/function**](https://github.com/micro/examples/tree/master/function).
|
||||
- **Pluggable Interfaces** - Go Micro makes use of Go interfaces for each distributed system abstraction. Because of this these interfaces
|
||||
are pluggable and allows Go Micro to be runtime agnostic. You can plugin any underlying technology. Find plugins in
|
||||
[github.com/micro/go-plugins](https://github.com/micro/go-plugins).
|
||||
|
||||
The [**examples**](https://github.com/micro/examples) directory contains examples for using things such as middleware/wrappers, selector filters, pub/sub, grpc, plugins and much more. For the complete greeter example look at [**examples/greeter**](https://github.com/micro/examples/tree/master/greeter). Other examples can be found throughout the GitHub repository.
|
||||
## Getting Started
|
||||
|
||||
Watch the [Golang UK Conf 2016](https://www.youtube.com/watch?v=xspaDovwk34) video for a high level overview.
|
||||
|
||||
## Getting started
|
||||
|
||||
- [Service Discovery](#service-discovery)
|
||||
- [Writing a Service](#writing-a-service)
|
||||
- [Writing a Function](#writing-a-function)
|
||||
- [Plugins](#plugins)
|
||||
- [Wrappers](#wrappers)
|
||||
|
||||
## Service Discovery
|
||||
|
||||
Service discovery is used to resolve service names to addresses. It's the only dependency of go-micro.
|
||||
|
||||
### Consul
|
||||
|
||||
[Consul](https://www.consul.io/) is used as the default service discovery system.
|
||||
|
||||
Discovery is pluggable. Find plugins for etcd, kubernetes, zookeeper and more in the [micro/go-plugins](https://github.com/micro/go-plugins) repo.
|
||||
|
||||
[Install guide](https://www.consul.io/intro/getting-started/install.html)
|
||||
|
||||
### Multicast DNS
|
||||
|
||||
[Multicast DNS](https://en.wikipedia.org/wiki/Multicast_DNS) is a built in service discovery plugin for a zero dependency configuration.
|
||||
|
||||
Pass `--registry=mdns` to any command or the enviroment variable MICRO_REGISTRY=mdns
|
||||
|
||||
```
|
||||
go run main.go --registry=mdns
|
||||
```
|
||||
|
||||
## Writing a service
|
||||
|
||||
This is a simple greeter RPC service example
|
||||
|
||||
Find this example at [examples/service](https://github.com/micro/examples/tree/master/service).
|
||||
|
||||
### Create service proto
|
||||
|
||||
One of the key requirements of microservices is strongly defined interfaces. Micro uses protobuf to achieve this.
|
||||
|
||||
Here we define the Greeter handler with the method Hello. It takes a HelloRequest and HelloResponse both with one string arguments.
|
||||
|
||||
```proto
|
||||
syntax = "proto3";
|
||||
|
||||
service Greeter {
|
||||
rpc Hello(HelloRequest) returns (HelloResponse) {}
|
||||
}
|
||||
|
||||
message HelloRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message HelloResponse {
|
||||
string greeting = 2;
|
||||
}
|
||||
```
|
||||
|
||||
### Install protobuf
|
||||
|
||||
Install [protobuf](https://developers.google.com/protocol-buffers/)
|
||||
|
||||
Now install the micro fork of protoc-gen-go. The protobuf compiler for Go.
|
||||
|
||||
```shell
|
||||
go get github.com/micro/protobuf/{proto,protoc-gen-go}
|
||||
```
|
||||
|
||||
### Generate the proto
|
||||
|
||||
After writing the proto definition we must compile it using protoc with the micro plugin.
|
||||
|
||||
```shell
|
||||
protoc -I$GOPATH/src --go_out=plugins=micro:$GOPATH/src \
|
||||
$GOPATH/src/github.com/micro/examples/service/proto/greeter.proto
|
||||
```
|
||||
|
||||
### Write the service
|
||||
|
||||
Below is the code for the greeter service.
|
||||
|
||||
It does the following:
|
||||
|
||||
1. Implements the interface defined for the Greeter handler
|
||||
2. Initialises a micro.Service
|
||||
3. Registers the Greeter handler
|
||||
4. Runs the service
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
micro "github.com/micro/go-micro"
|
||||
proto "github.com/micro/examples/service/proto"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Greeter struct{}
|
||||
|
||||
func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error {
|
||||
rsp.Greeting = "Hello " + req.Name
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Create a new service. Optionally include some options here.
|
||||
service := micro.NewService(
|
||||
micro.Name("greeter"),
|
||||
)
|
||||
|
||||
// Init will parse the command line flags.
|
||||
service.Init()
|
||||
|
||||
// Register handler
|
||||
proto.RegisterGreeterHandler(service.Server(), new(Greeter))
|
||||
|
||||
// Run the server
|
||||
if err := service.Run(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Run service
|
||||
```
|
||||
go run examples/service/main.go
|
||||
```
|
||||
|
||||
Output
|
||||
```
|
||||
2016/03/14 10:59:14 Listening on [::]:50137
|
||||
2016/03/14 10:59:14 Broker Listening on [::]:50138
|
||||
2016/03/14 10:59:14 Registering node: greeter-ca62b017-e9d3-11e5-9bbb-68a86d0d36b6
|
||||
```
|
||||
|
||||
### Define a client
|
||||
|
||||
Below is the client code to query the greeter service.
|
||||
|
||||
The generated proto includes a greeter client to reduce boilerplate code.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
micro "github.com/micro/go-micro"
|
||||
proto "github.com/micro/examples/service/proto"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
|
||||
func main() {
|
||||
// Create a new service. Optionally include some options here.
|
||||
service := micro.NewService(micro.Name("greeter.client"))
|
||||
|
||||
// Create new greeter client
|
||||
greeter := proto.NewGreeterClient("greeter", service.Client())
|
||||
|
||||
// Call the greeter
|
||||
rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{Name: "John"})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
// Print response
|
||||
fmt.Println(rsp.Greeting)
|
||||
}
|
||||
```
|
||||
|
||||
### Run the client
|
||||
|
||||
```shell
|
||||
go run client.go
|
||||
```
|
||||
|
||||
Output
|
||||
```
|
||||
Hello John
|
||||
```
|
||||
|
||||
## Writing a Function
|
||||
|
||||
Go Micro includes the Function programming model.
|
||||
|
||||
A Function is a one time executing Service which exits after completing a request.
|
||||
|
||||
### Defining a Function
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
proto "github.com/micro/examples/function/proto"
|
||||
"github.com/micro/go-micro"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Greeter struct{}
|
||||
|
||||
func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error {
|
||||
rsp.Greeting = "Hello " + req.Name
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// create a new function
|
||||
fnc := micro.NewFunction(
|
||||
micro.Name("go.micro.fnc.greeter"),
|
||||
)
|
||||
|
||||
// init the command line
|
||||
fnc.Init()
|
||||
|
||||
// register a handler
|
||||
fnc.Handle(new(Greeter))
|
||||
|
||||
// run the function
|
||||
fnc.Run()
|
||||
}
|
||||
```
|
||||
|
||||
It's that simple.
|
||||
|
||||
## Plugins
|
||||
|
||||
By default go-micro only provides a few implementation of each interface at the core but it's completely pluggable. There's already dozens of plugins which are available at [github.com/micro/go-plugins](https://github.com/micro/go-plugins). Contributions are welcome!
|
||||
|
||||
### Build with plugins
|
||||
|
||||
If you want to integrate plugins simply link them in a separate file and rebuild
|
||||
|
||||
Create a plugins.go file
|
||||
|
||||
```go
|
||||
import (
|
||||
// etcd v3 registry
|
||||
_ "github.com/micro/go-plugins/registry/etcdv3"
|
||||
// nats transport
|
||||
_ "github.com/micro/go-plugins/transport/nats"
|
||||
// kafka broker
|
||||
_ "github.com/micro/go-plugins/broker/kafka"
|
||||
)
|
||||
```
|
||||
|
||||
Build binary
|
||||
|
||||
```shell
|
||||
// For local use
|
||||
go build -i -o service ./main.go ./plugins.go
|
||||
```
|
||||
|
||||
Flag usage of plugins
|
||||
```shell
|
||||
service --registry=etcdv3 --transport=nats --broker=kafka
|
||||
```
|
||||
|
||||
## Wrappers
|
||||
|
||||
Go-micro includes the notion of middleware as wrappers. The client or handlers can be wrapped using the decorator pattern.
|
||||
|
||||
### Handler
|
||||
|
||||
Here's an example service handler wrapper which logs the incoming request
|
||||
|
||||
```go
|
||||
// implements the server.HandlerWrapper
|
||||
func logWrapper(fn server.HandlerFunc) server.HandlerFunc {
|
||||
return func(ctx context.Context, req server.Request, rsp interface{}) error {
|
||||
fmt.Printf("[%v] server request: %s", time.Now(), req.Method())
|
||||
return fn(ctx, req, rsp)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It can be initialised when creating the service
|
||||
|
||||
```go
|
||||
service := micro.NewService(
|
||||
micro.Name("greeter"),
|
||||
// wrap the handler
|
||||
micro.WrapHandler(logWrapper),
|
||||
)
|
||||
```
|
||||
|
||||
### Client
|
||||
|
||||
Here's an example of a client wrapper which logs requests made
|
||||
|
||||
```go
|
||||
type logWrapper struct {
|
||||
client.Client
|
||||
}
|
||||
|
||||
func (l *logWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
|
||||
fmt.Printf("[wrapper] client request to service: %s method: %s\n", req.Service(), req.Method())
|
||||
return l.Client.Call(ctx, req, rsp)
|
||||
}
|
||||
|
||||
// implements client.Wrapper as logWrapper
|
||||
func logWrap(c client.Client) client.Client {
|
||||
return &logWrapper{c}
|
||||
}
|
||||
```
|
||||
|
||||
It can be initialised when creating the service
|
||||
|
||||
```go
|
||||
service := micro.NewService(
|
||||
micro.Name("greeter"),
|
||||
// wrap the client
|
||||
micro.WrapClient(logWrap),
|
||||
)
|
||||
```
|
||||
|
||||
## Other Languages
|
||||
|
||||
Check out [ja-micro](https://github.com/Sixt/ja-micro) to write services in Java
|
||||
See the [docs](https://micro.mu/docs/go-micro.html) for detailed information on the architecture, installation and use of go-micro.
|
||||
|
||||
## Sponsors
|
||||
|
||||
Open source development of Micro is sponsored by Sixt
|
||||
Sixt is an Enterprise Sponsor of Micro
|
||||
|
||||
<a href="https://micro.mu/blog/2016/04/25/announcing-sixt-sponsorship.html"><img src="https://micro.mu/sixt_logo.png" width=150px height="auto" /></a>
|
||||
|
||||
Become a sponsor by backing micro on [Patreon](https://www.patreon.com/microhq)
|
||||
|
@@ -1,9 +1,11 @@
|
||||
// Package http provides a http based message broker
|
||||
package http
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/broker"
|
||||
)
|
||||
|
||||
// NewBroker returns a new http broker
|
||||
func NewBroker(opts ...broker.Option) broker.Broker {
|
||||
return broker.NewBroker(opts...)
|
||||
}
|
||||
|
23
broker/http/options.go
Normal file
23
broker/http/options.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/micro/go-micro/broker"
|
||||
)
|
||||
|
||||
// Handle registers the handler for the given pattern.
|
||||
func Handle(pattern string, handler http.Handler) broker.Option {
|
||||
return func(o *broker.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
handlers, ok := o.Context.Value("http_handlers").(map[string]http.Handler)
|
||||
if !ok {
|
||||
handlers = make(map[string]http.Handler)
|
||||
}
|
||||
handlers[pattern] = handler
|
||||
o.Context = context.WithValue(o.Context, "http_handlers", handlers)
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@ package broker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -17,17 +18,16 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-log"
|
||||
"github.com/micro/go-micro/broker/codec/json"
|
||||
merr "github.com/micro/go-micro/errors"
|
||||
"github.com/micro/go-micro/registry"
|
||||
"github.com/micro/go-rcache"
|
||||
maddr "github.com/micro/misc/lib/addr"
|
||||
mnet "github.com/micro/misc/lib/net"
|
||||
mls "github.com/micro/misc/lib/tls"
|
||||
"github.com/pborman/uuid"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
maddr "github.com/micro/util/go/lib/addr"
|
||||
mnet "github.com/micro/util/go/lib/net"
|
||||
mls "github.com/micro/util/go/lib/tls"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
// HTTP Broker is a point to point async broker
|
||||
@@ -79,6 +79,10 @@ func newTransport(config *tls.Config) *http.Transport {
|
||||
}
|
||||
}
|
||||
|
||||
dialTLS := func(network string, addr string) (net.Conn, error) {
|
||||
return tls.Dial(network, addr, config)
|
||||
}
|
||||
|
||||
t := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
@@ -86,11 +90,15 @@ func newTransport(config *tls.Config) *http.Transport {
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSClientConfig: config,
|
||||
DialTLS: dialTLS,
|
||||
}
|
||||
runtime.SetFinalizer(&t, func(tr **http.Transport) {
|
||||
(*tr).CloseIdleConnections()
|
||||
})
|
||||
|
||||
// setup http2
|
||||
http2.ConfigureTransport(t)
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
@@ -117,7 +125,7 @@ func newHttpBroker(opts ...Option) Broker {
|
||||
}
|
||||
|
||||
h := &httpBroker{
|
||||
id: "broker-" + uuid.NewUUID().String(),
|
||||
id: "broker-" + uuid.New().String(),
|
||||
address: addr,
|
||||
opts: options,
|
||||
r: reg,
|
||||
@@ -127,7 +135,19 @@ func newHttpBroker(opts ...Option) Broker {
|
||||
mux: http.NewServeMux(),
|
||||
}
|
||||
|
||||
// specify the message handler
|
||||
h.mux.Handle(DefaultSubPath, h)
|
||||
|
||||
// get optional handlers
|
||||
if h.opts.Context != nil {
|
||||
handlers, ok := h.opts.Context.Value("http_handlers").(map[string]http.Handler)
|
||||
if ok {
|
||||
for pattern, handler := range handlers {
|
||||
h.mux.Handle(pattern, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
@@ -177,7 +197,7 @@ func (h *httpBroker) unsubscribe(s *httpSubscriber) error {
|
||||
for _, sub := range h.subscribers[s.topic] {
|
||||
// deregister and skip forward
|
||||
if sub.id == s.id {
|
||||
h.r.Deregister(sub.svc)
|
||||
_ = h.r.Deregister(sub.svc)
|
||||
continue
|
||||
}
|
||||
// keep subscriber
|
||||
@@ -201,7 +221,7 @@ func (h *httpBroker) run(l net.Listener) {
|
||||
h.RLock()
|
||||
for _, subs := range h.subscribers {
|
||||
for _, sub := range subs {
|
||||
h.r.Register(sub.svc, registry.RegisterTTL(registerTTL))
|
||||
_ = h.r.Register(sub.svc, registry.RegisterTTL(registerTTL))
|
||||
}
|
||||
}
|
||||
h.RUnlock()
|
||||
@@ -211,7 +231,7 @@ func (h *httpBroker) run(l net.Listener) {
|
||||
h.RLock()
|
||||
for _, subs := range h.subscribers {
|
||||
for _, sub := range subs {
|
||||
h.r.Deregister(sub.svc)
|
||||
_ = h.r.Deregister(sub.svc)
|
||||
}
|
||||
}
|
||||
h.RUnlock()
|
||||
@@ -277,12 +297,15 @@ func (h *httpBroker) Address() string {
|
||||
}
|
||||
|
||||
func (h *httpBroker) Connect() error {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
h.RLock()
|
||||
if h.running {
|
||||
h.RUnlock()
|
||||
return nil
|
||||
}
|
||||
h.RUnlock()
|
||||
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
var l net.Listener
|
||||
var err error
|
||||
@@ -327,10 +350,16 @@ func (h *httpBroker) Connect() error {
|
||||
}
|
||||
|
||||
log.Logf("Broker Listening on %s", l.Addr().String())
|
||||
addr := h.address
|
||||
h.address = l.Addr().String()
|
||||
|
||||
go http.Serve(l, h.mux)
|
||||
go h.run(l)
|
||||
go func() {
|
||||
h.run(l)
|
||||
h.Lock()
|
||||
h.address = addr
|
||||
h.Unlock()
|
||||
}()
|
||||
|
||||
// get registry
|
||||
reg, ok := h.opts.Context.Value(registryKey).(registry.Registry)
|
||||
@@ -346,12 +375,16 @@ func (h *httpBroker) Connect() error {
|
||||
}
|
||||
|
||||
func (h *httpBroker) Disconnect() error {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
h.RLock()
|
||||
if !h.running {
|
||||
h.RUnlock()
|
||||
return nil
|
||||
}
|
||||
h.RUnlock()
|
||||
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
// stop rcache
|
||||
rc, ok := h.r.(rcache.Cache)
|
||||
@@ -370,19 +403,26 @@ func (h *httpBroker) Disconnect() error {
|
||||
}
|
||||
|
||||
func (h *httpBroker) Init(opts ...Option) error {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
h.RLock()
|
||||
if h.running {
|
||||
h.RUnlock()
|
||||
return errors.New("cannot init while connected")
|
||||
}
|
||||
h.RUnlock()
|
||||
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
for _, o := range opts {
|
||||
o(&h.opts)
|
||||
}
|
||||
|
||||
if len(h.opts.Addrs) > 0 && len(h.opts.Addrs[0]) > 0 {
|
||||
h.address = h.opts.Addrs[0]
|
||||
}
|
||||
|
||||
if len(h.id) == 0 {
|
||||
h.id = "broker-" + uuid.NewUUID().String()
|
||||
h.id = "broker-" + uuid.New().String()
|
||||
}
|
||||
|
||||
// get registry
|
||||
@@ -399,6 +439,13 @@ func (h *httpBroker) Init(opts ...Option) error {
|
||||
// set registry
|
||||
h.r = rcache.New(reg)
|
||||
|
||||
// reconfigure tls config
|
||||
if c := h.opts.TLSConfig; c != nil {
|
||||
h.c = &http.Client{
|
||||
Transport: newTransport(c),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -476,7 +523,7 @@ func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption)
|
||||
}
|
||||
|
||||
func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
|
||||
options := newSubscribeOptions(opts...)
|
||||
options := NewSubscribeOptions(opts...)
|
||||
|
||||
// parse address for host, port
|
||||
parts := strings.Split(h.Address(), ":")
|
||||
@@ -489,7 +536,7 @@ func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeO
|
||||
}
|
||||
|
||||
// create unique id
|
||||
id := h.id + "." + uuid.NewUUID().String()
|
||||
id := h.id + "." + uuid.New().String()
|
||||
|
||||
var secure bool
|
||||
|
||||
|
@@ -5,15 +5,15 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/registry/mock"
|
||||
"github.com/pborman/uuid"
|
||||
)
|
||||
|
||||
func sub(be *testing.B, c int) {
|
||||
be.StopTimer()
|
||||
m := mock.NewRegistry()
|
||||
b := NewBroker(Registry(m))
|
||||
topic := uuid.NewUUID().String()
|
||||
topic := uuid.New().String()
|
||||
|
||||
if err := b.Init(); err != nil {
|
||||
be.Fatalf("Unexpected init error: %v", err)
|
||||
@@ -72,7 +72,7 @@ func pub(be *testing.B, c int) {
|
||||
be.StopTimer()
|
||||
m := mock.NewRegistry()
|
||||
b := NewBroker(Registry(m))
|
||||
topic := uuid.NewUUID().String()
|
||||
topic := uuid.New().String()
|
||||
|
||||
if err := b.Init(); err != nil {
|
||||
be.Fatalf("Unexpected init error: %v", err)
|
||||
|
@@ -1,11 +1,12 @@
|
||||
// Package mock provides a mock broker for testing
|
||||
package mock
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/broker"
|
||||
"github.com/pborman/uuid"
|
||||
)
|
||||
|
||||
type mockBroker struct {
|
||||
@@ -112,7 +113,7 @@ func (m *mockBroker) Subscribe(topic string, handler broker.Handler, opts ...bro
|
||||
|
||||
sub := &mockSubscriber{
|
||||
exit: make(chan bool, 1),
|
||||
id: uuid.NewUUID().String(),
|
||||
id: uuid.New().String(),
|
||||
topic: topic,
|
||||
handler: handler,
|
||||
opts: options,
|
||||
|
@@ -1,11 +1,11 @@
|
||||
package broker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/micro/go-micro/broker/codec"
|
||||
"github.com/micro/go-micro/registry"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
@@ -50,7 +50,7 @@ var (
|
||||
registryKey = contextKeyT("github.com/micro/go-micro/registry")
|
||||
)
|
||||
|
||||
func newSubscribeOptions(opts ...SubscribeOption) SubscribeOptions {
|
||||
func NewSubscribeOptions(opts ...SubscribeOption) SubscribeOptions {
|
||||
opt := SubscribeOptions{
|
||||
AutoAck: true,
|
||||
}
|
||||
|
@@ -1,10 +1,9 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type BackoffFunc func(ctx context.Context, req Request, attempts int) (time.Duration, error)
|
||||
|
@@ -1,18 +1,19 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestBackoff(t *testing.T) {
|
||||
delta := time.Duration(0)
|
||||
|
||||
c := NewClient()
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
d, err := exponentialBackoff(context.TODO(), NewJsonRequest("test", "test", nil), i)
|
||||
d, err := exponentialBackoff(context.TODO(), c.NewRequest("test", "test", nil), i)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@@ -2,9 +2,8 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Client is the interface used to make requests to services.
|
||||
@@ -13,22 +12,18 @@ import (
|
||||
type Client interface {
|
||||
Init(...Option) error
|
||||
Options() Options
|
||||
NewPublication(topic string, msg interface{}) Publication
|
||||
NewMessage(topic string, msg interface{}, opts ...MessageOption) Message
|
||||
NewRequest(service, method string, req interface{}, reqOpts ...RequestOption) Request
|
||||
NewProtoRequest(service, method string, req interface{}, reqOpts ...RequestOption) Request
|
||||
NewJsonRequest(service, method string, req interface{}, reqOpts ...RequestOption) Request
|
||||
Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error
|
||||
CallRemote(ctx context.Context, addr string, req Request, rsp interface{}, opts ...CallOption) error
|
||||
Stream(ctx context.Context, req Request, opts ...CallOption) (Streamer, error)
|
||||
StreamRemote(ctx context.Context, addr string, req Request, opts ...CallOption) (Streamer, error)
|
||||
Publish(ctx context.Context, p Publication, opts ...PublishOption) error
|
||||
Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error)
|
||||
Publish(ctx context.Context, msg Message, opts ...PublishOption) error
|
||||
String() string
|
||||
}
|
||||
|
||||
// Publication is the interface for a message published asynchronously
|
||||
type Publication interface {
|
||||
// Message is the interface for publishing asynchronously
|
||||
type Message interface {
|
||||
Topic() string
|
||||
Message() interface{}
|
||||
Payload() interface{}
|
||||
ContentType() string
|
||||
}
|
||||
|
||||
@@ -42,8 +37,8 @@ type Request interface {
|
||||
Stream() bool
|
||||
}
|
||||
|
||||
// Streamer is the inteface for a bidirectional synchronous stream
|
||||
type Streamer interface {
|
||||
// Stream is the inteface for a bidirectional synchronous stream
|
||||
type Stream interface {
|
||||
Context() context.Context
|
||||
Request() Request
|
||||
Send(interface{}) error
|
||||
@@ -61,6 +56,9 @@ type CallOption func(*CallOptions)
|
||||
// PublishOption used by Publish
|
||||
type PublishOption func(*PublishOptions)
|
||||
|
||||
// MessageOption used by NewMessage
|
||||
type MessageOption func(*MessageOptions)
|
||||
|
||||
// RequestOption used by NewRequest
|
||||
type RequestOption func(*RequestOptions)
|
||||
|
||||
@@ -70,13 +68,13 @@ var (
|
||||
// DefaultBackoff is the default backoff function for retries
|
||||
DefaultBackoff = exponentialBackoff
|
||||
// DefaultRetry is the default check-for-retry function for retries
|
||||
DefaultRetry = alwaysRetry
|
||||
DefaultRetry = RetryOnError
|
||||
// DefaultRetries is the default number of times a request is tried
|
||||
DefaultRetries = 1
|
||||
// DefaultRequestTimeout is the default request timeout
|
||||
DefaultRequestTimeout = time.Second * 5
|
||||
// DefaultPoolSize sets the connection pool size
|
||||
DefaultPoolSize = 0
|
||||
DefaultPoolSize = 1
|
||||
// DefaultPoolTTL sets the connection pool ttl
|
||||
DefaultPoolTTL = time.Minute
|
||||
)
|
||||
@@ -86,26 +84,15 @@ func Call(ctx context.Context, request Request, response interface{}, opts ...Ca
|
||||
return DefaultClient.Call(ctx, request, response, opts...)
|
||||
}
|
||||
|
||||
// Makes a synchronous call to the specified address using the default client
|
||||
func CallRemote(ctx context.Context, address string, request Request, response interface{}, opts ...CallOption) error {
|
||||
return DefaultClient.CallRemote(ctx, address, request, response, opts...)
|
||||
}
|
||||
|
||||
// Creates a streaming connection with a service and returns responses on the
|
||||
// channel passed in. It's up to the user to close the streamer.
|
||||
func Stream(ctx context.Context, request Request, opts ...CallOption) (Streamer, error) {
|
||||
return DefaultClient.Stream(ctx, request, opts...)
|
||||
}
|
||||
|
||||
// Creates a streaming connection to the address specified.
|
||||
func StreamRemote(ctx context.Context, address string, request Request, opts ...CallOption) (Streamer, error) {
|
||||
return DefaultClient.StreamRemote(ctx, address, request, opts...)
|
||||
}
|
||||
|
||||
// Publishes a publication using the default client. Using the underlying broker
|
||||
// set within the options.
|
||||
func Publish(ctx context.Context, p Publication) error {
|
||||
return DefaultClient.Publish(ctx, p)
|
||||
func Publish(ctx context.Context, msg Message, opts ...PublishOption) error {
|
||||
return DefaultClient.Publish(ctx, msg, opts...)
|
||||
}
|
||||
|
||||
// Creates a new message using the default client
|
||||
func NewMessage(topic string, payload interface{}, opts ...MessageOption) Message {
|
||||
return DefaultClient.NewMessage(topic, payload, opts...)
|
||||
}
|
||||
|
||||
// Creates a new client with the options passed in
|
||||
@@ -113,25 +100,16 @@ func NewClient(opt ...Option) Client {
|
||||
return newRpcClient(opt...)
|
||||
}
|
||||
|
||||
// Creates a new publication using the default client
|
||||
func NewPublication(topic string, message interface{}) Publication {
|
||||
return DefaultClient.NewPublication(topic, message)
|
||||
}
|
||||
|
||||
// Creates a new request using the default client. Content Type will
|
||||
// be set to the default within options and use the appropriate codec
|
||||
func NewRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
|
||||
return DefaultClient.NewRequest(service, method, request, reqOpts...)
|
||||
}
|
||||
|
||||
// Creates a new protobuf request using the default client
|
||||
func NewProtoRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
|
||||
return DefaultClient.NewProtoRequest(service, method, request, reqOpts...)
|
||||
}
|
||||
|
||||
// Creates a new json request using the default client
|
||||
func NewJsonRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
|
||||
return DefaultClient.NewJsonRequest(service, method, request, reqOpts...)
|
||||
// Creates a streaming connection with a service and returns responses on the
|
||||
// channel passed in. It's up to the user to close the streamer.
|
||||
func NewStream(ctx context.Context, request Request, opts ...CallOption) (Stream, error) {
|
||||
return DefaultClient.Stream(ctx, request, opts...)
|
||||
}
|
||||
|
||||
func String() string {
|
||||
|
@@ -1,51 +0,0 @@
|
||||
package client
|
||||
|
||||
/*
|
||||
Wrapper is a type of middleware for the go-micro client. It allows
|
||||
the client to be "wrapped" so that requests and responses can be intercepted
|
||||
to perform extra requirements such as auth, tracing, monitoring, logging, etc.
|
||||
|
||||
Example usage:
|
||||
|
||||
import (
|
||||
"log"
|
||||
"github.com/micro/go-micro/client"
|
||||
|
||||
)
|
||||
|
||||
type LogWrapper struct {
|
||||
client.Client
|
||||
}
|
||||
|
||||
func (l *LogWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
|
||||
log.Println("Making request to service " + req.Service() + " method " + req.Method())
|
||||
return w.Client.Call(ctx, req, rsp)
|
||||
}
|
||||
|
||||
func Wrapper(c client.Client) client.Client {
|
||||
return &LogWrapper{c}
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := client.NewClient(client.Wrap(Wrapper))
|
||||
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// CallFunc represents the individual call func
|
||||
type CallFunc func(ctx context.Context, address string, req Request, rsp interface{}, opts CallOptions) error
|
||||
|
||||
// CallWrapper is a low level wrapper for the CallFunc
|
||||
type CallWrapper func(CallFunc) CallFunc
|
||||
|
||||
// Wrapper wraps a client and returns a client
|
||||
type Wrapper func(Client) Client
|
||||
|
||||
// StreamWrapper wraps a Stream and returns the equivalent
|
||||
type StreamWrapper func(Streamer) Streamer
|
@@ -1,7 +1,7 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"context"
|
||||
)
|
||||
|
||||
type clientKey struct{}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"context"
|
||||
)
|
||||
|
||||
type responseKey struct{}
|
||||
|
@@ -1,14 +1,14 @@
|
||||
// Package mock provides a mock client for testing
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/micro/go-micro/client"
|
||||
"github.com/micro/go-micro/errors"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -50,22 +50,14 @@ func (m *MockClient) Options() client.Options {
|
||||
return m.Opts
|
||||
}
|
||||
|
||||
func (m *MockClient) NewPublication(topic string, msg interface{}) client.Publication {
|
||||
return m.Client.NewPublication(topic, msg)
|
||||
func (m *MockClient) NewMessage(topic string, msg interface{}, opts ...client.MessageOption) client.Message {
|
||||
return m.Client.NewMessage(topic, msg, opts...)
|
||||
}
|
||||
|
||||
func (m *MockClient) NewRequest(service, method string, req interface{}, reqOpts ...client.RequestOption) client.Request {
|
||||
return m.Client.NewRequest(service, method, req, reqOpts...)
|
||||
}
|
||||
|
||||
func (m *MockClient) NewProtoRequest(service, method string, req interface{}, reqOpts ...client.RequestOption) client.Request {
|
||||
return m.Client.NewProtoRequest(service, method, req, reqOpts...)
|
||||
}
|
||||
|
||||
func (m *MockClient) NewJsonRequest(service, method string, req interface{}, reqOpts ...client.RequestOption) client.Request {
|
||||
return m.Client.NewJsonRequest(service, method, req, reqOpts...)
|
||||
}
|
||||
|
||||
func (m *MockClient) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
@@ -89,8 +81,12 @@ func (m *MockClient) Call(ctx context.Context, req client.Request, rsp interface
|
||||
if t := reflect.TypeOf(rsp); t.Kind() == reflect.Ptr {
|
||||
v = reflect.Indirect(v)
|
||||
}
|
||||
response := r.Response
|
||||
if t := reflect.TypeOf(r.Response); t.Kind() == reflect.Func {
|
||||
response = reflect.ValueOf(r.Response).Call([]reflect.Value{})[0].Interface()
|
||||
}
|
||||
|
||||
v.Set(reflect.ValueOf(r.Response))
|
||||
v.Set(reflect.ValueOf(response))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -98,39 +94,7 @@ func (m *MockClient) Call(ctx context.Context, req client.Request, rsp interface
|
||||
return fmt.Errorf("rpc: can't find service %s", req.Method())
|
||||
}
|
||||
|
||||
func (m *MockClient) CallRemote(ctx context.Context, addr string, req client.Request, rsp interface{}, opts ...client.CallOption) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
response, ok := m.Response[req.Service()]
|
||||
if !ok {
|
||||
return errors.NotFound("go.micro.client.mock", "service not found")
|
||||
}
|
||||
|
||||
for _, r := range response {
|
||||
if r.Method != req.Method() {
|
||||
continue
|
||||
}
|
||||
|
||||
if r.Error != nil {
|
||||
return r.Error
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(rsp)
|
||||
|
||||
if t := reflect.TypeOf(rsp); t.Kind() == reflect.Ptr {
|
||||
v = reflect.Indirect(v)
|
||||
}
|
||||
|
||||
v.Set(reflect.ValueOf(r.Response))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("rpc: can't find service %s", req.Method())
|
||||
}
|
||||
|
||||
func (m *MockClient) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Streamer, error) {
|
||||
func (m *MockClient) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
@@ -138,15 +102,7 @@ func (m *MockClient) Stream(ctx context.Context, req client.Request, opts ...cli
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockClient) StreamRemote(ctx context.Context, addr string, req client.Request, opts ...client.CallOption) (client.Streamer, error) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
// TODO: mock stream
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockClient) Publish(ctx context.Context, p client.Publication, opts ...client.PublishOption) error {
|
||||
func (m *MockClient) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -1,11 +1,10 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/errors"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestClient(t *testing.T) {
|
||||
@@ -17,12 +16,14 @@ func TestClient(t *testing.T) {
|
||||
{Method: "Foo.Bar", Response: map[string]interface{}{"foo": "bar"}},
|
||||
{Method: "Foo.Struct", Response: &TestResponse{Param: "aparam"}},
|
||||
{Method: "Foo.Fail", Error: errors.InternalServerError("go.mock", "failed")},
|
||||
{Method: "Foo.Func", Response: func() string { return "string" }},
|
||||
{Method: "Foo.FuncStruct", Response: func() *TestResponse { return &TestResponse{Param: "aparam"} }},
|
||||
}
|
||||
|
||||
c := NewClient(Response("go.mock", response))
|
||||
|
||||
for _, r := range response {
|
||||
req := c.NewJsonRequest("go.mock", r.Method, map[string]interface{}{"foo": "bar"})
|
||||
req := c.NewRequest("go.mock", r.Method, map[string]interface{}{"foo": "bar"})
|
||||
var rsp interface{}
|
||||
|
||||
err := c.Call(context.TODO(), req, &rsp)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/broker"
|
||||
@@ -8,8 +9,6 @@ import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
"github.com/micro/go-micro/selector"
|
||||
"github.com/micro/go-micro/transport"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
@@ -41,6 +40,8 @@ type Options struct {
|
||||
type CallOptions struct {
|
||||
SelectOptions []selector.SelectOption
|
||||
|
||||
// Address of remote host
|
||||
Address string
|
||||
// Backoff func
|
||||
Backoff BackoffFunc
|
||||
// Check if retriable func
|
||||
@@ -66,8 +67,13 @@ type PublishOptions struct {
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
type MessageOptions struct {
|
||||
ContentType string
|
||||
}
|
||||
|
||||
type RequestOptions struct {
|
||||
Stream bool
|
||||
ContentType string
|
||||
Stream bool
|
||||
|
||||
// Other options for implementations of the interface
|
||||
// can be stored in a context
|
||||
@@ -227,6 +233,13 @@ func DialTimeout(d time.Duration) Option {
|
||||
|
||||
// Call Options
|
||||
|
||||
// WithAddress sets the remote address to use rather than using service discovery
|
||||
func WithAddress(a string) CallOption {
|
||||
return func(o *CallOptions) {
|
||||
o.Address = a
|
||||
}
|
||||
}
|
||||
|
||||
func WithSelectOption(so ...selector.SelectOption) CallOption {
|
||||
return func(o *CallOptions) {
|
||||
o.SelectOptions = append(o.SelectOptions, so...)
|
||||
@@ -282,6 +295,12 @@ func WithDialTimeout(d time.Duration) CallOption {
|
||||
|
||||
// Request Options
|
||||
|
||||
func WithContentType(ct string) RequestOption {
|
||||
return func(o *RequestOptions) {
|
||||
o.ContentType = ct
|
||||
}
|
||||
}
|
||||
|
||||
func StreamingRequest() RequestOption {
|
||||
return func(o *RequestOptions) {
|
||||
o.Stream = true
|
||||
|
@@ -1,11 +1,35 @@
|
||||
package client
|
||||
|
||||
import "golang.org/x/net/context"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/errors"
|
||||
)
|
||||
|
||||
// note that returning either false or a non-nil error will result in the call not being retried
|
||||
type RetryFunc func(ctx context.Context, req Request, retryCount int, err error) (bool, error)
|
||||
|
||||
// always retry on error
|
||||
func alwaysRetry(ctx context.Context, req Request, retryCount int, err error) (bool, error) {
|
||||
// RetryAlways always retry on error
|
||||
func RetryAlways(ctx context.Context, req Request, retryCount int, err error) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// RetryOnError retries a request on a 500 or timeout error
|
||||
func RetryOnError(ctx context.Context, req Request, retryCount int, err error) (bool, error) {
|
||||
if err == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
e := errors.Parse(err.Error())
|
||||
if e == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
switch e.Code {
|
||||
// retry on timeout or internal server error
|
||||
case 408, 500:
|
||||
return true, nil
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,11 @@
|
||||
// Package rpc provides an rpc client
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/client"
|
||||
)
|
||||
|
||||
// NewClient returns a new micro client interface
|
||||
func NewClient(opts ...client.Option) client.Client {
|
||||
return client.NewClient(opts...)
|
||||
}
|
||||
|
@@ -2,24 +2,27 @@ package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/micro/go-micro/broker"
|
||||
"github.com/micro/go-micro/codec"
|
||||
"github.com/micro/go-micro/errors"
|
||||
"github.com/micro/go-micro/metadata"
|
||||
"github.com/micro/go-micro/registry"
|
||||
"github.com/micro/go-micro/selector"
|
||||
"github.com/micro/go-micro/transport"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type rpcClient struct {
|
||||
once sync.Once
|
||||
opts Options
|
||||
pool *pool
|
||||
seq uint64
|
||||
}
|
||||
|
||||
func newRpcClient(opt ...Option) Client {
|
||||
@@ -29,6 +32,7 @@ func newRpcClient(opt ...Option) Client {
|
||||
once: sync.Once{},
|
||||
opts: opts,
|
||||
pool: newPool(opts.PoolSize, opts.PoolTTL),
|
||||
seq: 0,
|
||||
}
|
||||
|
||||
c := Client(rc)
|
||||
@@ -85,11 +89,15 @@ func (r *rpcClient) call(ctx context.Context, address string, req Request, resp
|
||||
r.pool.release(address, c, grr)
|
||||
}()
|
||||
|
||||
seq := atomic.LoadUint64(&r.seq)
|
||||
atomic.AddUint64(&r.seq, 1)
|
||||
|
||||
stream := &rpcStream{
|
||||
context: ctx,
|
||||
request: req,
|
||||
closed: make(chan bool),
|
||||
codec: newRpcPlusCodec(msg, c, cf),
|
||||
seq: seq,
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
@@ -124,11 +132,11 @@ func (r *rpcClient) call(ctx context.Context, address string, req Request, resp
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
grr = ctx.Err()
|
||||
return errors.New("go.micro.client", fmt.Sprintf("request timeout: %v", ctx.Err()), 408)
|
||||
return errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rpcClient) stream(ctx context.Context, address string, req Request, opts CallOptions) (Streamer, error) {
|
||||
func (r *rpcClient) stream(ctx context.Context, address string, req Request, opts CallOptions) (Stream, error) {
|
||||
msg := &transport.Message{
|
||||
Header: make(map[string]string),
|
||||
}
|
||||
@@ -152,7 +160,15 @@ func (r *rpcClient) stream(ctx context.Context, address string, req Request, opt
|
||||
return nil, errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
|
||||
c, err := r.opts.Transport.Dial(address, transport.WithStream(), transport.WithTimeout(opts.DialTimeout))
|
||||
dOpts := []transport.DialOption{
|
||||
transport.WithStream(),
|
||||
}
|
||||
|
||||
if opts.DialTimeout >= 0 {
|
||||
dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
|
||||
}
|
||||
|
||||
c, err := r.opts.Transport.Dial(address, dOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServerError("go.micro.client", "connection error: %v", err)
|
||||
}
|
||||
@@ -176,7 +192,7 @@ func (r *rpcClient) stream(ctx context.Context, address string, req Request, opt
|
||||
case err := <-ch:
|
||||
grr = err
|
||||
case <-ctx.Done():
|
||||
grr = errors.New("go.micro.client", fmt.Sprintf("request timeout: %v", ctx.Err()), 408)
|
||||
grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
|
||||
}
|
||||
|
||||
if grr != nil {
|
||||
@@ -195,9 +211,12 @@ func (r *rpcClient) Init(opts ...Option) error {
|
||||
o(&r.opts)
|
||||
}
|
||||
|
||||
// recreate the pool if the options changed
|
||||
// update pool configuration if the options changed
|
||||
if size != r.opts.PoolSize || ttl != r.opts.PoolTTL {
|
||||
r.pool = newPool(r.opts.PoolSize, r.opts.PoolTTL)
|
||||
r.pool.Lock()
|
||||
r.pool.size = r.opts.PoolSize
|
||||
r.pool.ttl = int64(r.opts.PoolTTL.Seconds())
|
||||
r.pool.Unlock()
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -207,13 +226,25 @@ func (r *rpcClient) Options() Options {
|
||||
return r.opts
|
||||
}
|
||||
|
||||
func (r *rpcClient) CallRemote(ctx context.Context, address string, request Request, response interface{}, opts ...CallOption) error {
|
||||
// make a copy of call opts
|
||||
callOpts := r.opts.CallOptions
|
||||
for _, opt := range opts {
|
||||
opt(&callOpts)
|
||||
func (r *rpcClient) next(request Request, opts CallOptions) (selector.Next, error) {
|
||||
// return remote address
|
||||
if len(opts.Address) > 0 {
|
||||
return func() (*registry.Node, error) {
|
||||
return ®istry.Node{
|
||||
Address: opts.Address,
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
return r.call(ctx, address, request, response, callOpts)
|
||||
|
||||
// get next nodes from the selector
|
||||
next, err := r.opts.Selector.Select(request.Service(), opts.SelectOptions...)
|
||||
if err != nil && err == selector.ErrNotFound {
|
||||
return nil, errors.NotFound("go.micro.client", "service %s: %v", request.Service(), err.Error())
|
||||
} else if err != nil {
|
||||
return nil, errors.InternalServerError("go.micro.client", "error selecting %s node: %v", request.Service(), err.Error())
|
||||
}
|
||||
|
||||
return next, nil
|
||||
}
|
||||
|
||||
func (r *rpcClient) Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error {
|
||||
@@ -223,12 +254,9 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
|
||||
opt(&callOpts)
|
||||
}
|
||||
|
||||
// get next nodes from the selector
|
||||
next, err := r.opts.Selector.Select(request.Service(), callOpts.SelectOptions...)
|
||||
if err != nil && err == selector.ErrNotFound {
|
||||
return errors.NotFound("go.micro.client", err.Error())
|
||||
} else if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
next, err := r.next(request, callOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if we already have a deadline
|
||||
@@ -246,7 +274,7 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
|
||||
// should we noop right here?
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
|
||||
return errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -263,7 +291,7 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
|
||||
// call backoff first. Someone may want an initial start delay
|
||||
t, err := callOpts.Backoff(ctx, request, i)
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
return errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error())
|
||||
}
|
||||
|
||||
// only sleep if greater than 0
|
||||
@@ -274,9 +302,9 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
|
||||
// select next node
|
||||
node, err := next()
|
||||
if err != nil && err == selector.ErrNotFound {
|
||||
return errors.NotFound("go.micro.client", err.Error())
|
||||
return errors.NotFound("go.micro.client", "service %s: %v", request.Service(), err.Error())
|
||||
} else if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
return errors.InternalServerError("go.micro.client", "error getting next %s node: %v", request.Service(), err.Error())
|
||||
}
|
||||
|
||||
// set the address
|
||||
@@ -294,14 +322,14 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
|
||||
ch := make(chan error, callOpts.Retries)
|
||||
var gerr error
|
||||
|
||||
for i := 0; i < callOpts.Retries; i++ {
|
||||
go func() {
|
||||
for i := 0; i <= callOpts.Retries; i++ {
|
||||
go func(i int) {
|
||||
ch <- call(i)
|
||||
}()
|
||||
}(i)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.New("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()), 408)
|
||||
return errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
|
||||
case err := <-ch:
|
||||
// if the call succeeded lets bail early
|
||||
if err == nil {
|
||||
@@ -324,54 +352,30 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
|
||||
return gerr
|
||||
}
|
||||
|
||||
func (r *rpcClient) StreamRemote(ctx context.Context, address string, request Request, opts ...CallOption) (Streamer, error) {
|
||||
// make a copy of call opts
|
||||
callOpts := r.opts.CallOptions
|
||||
for _, opt := range opts {
|
||||
opt(&callOpts)
|
||||
}
|
||||
return r.stream(ctx, address, request, callOpts)
|
||||
}
|
||||
|
||||
func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOption) (Streamer, error) {
|
||||
func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOption) (Stream, error) {
|
||||
// make a copy of call opts
|
||||
callOpts := r.opts.CallOptions
|
||||
for _, opt := range opts {
|
||||
opt(&callOpts)
|
||||
}
|
||||
|
||||
// get next nodes from the selector
|
||||
next, err := r.opts.Selector.Select(request.Service(), callOpts.SelectOptions...)
|
||||
if err != nil && err == selector.ErrNotFound {
|
||||
return nil, errors.NotFound("go.micro.client", err.Error())
|
||||
} else if err != nil {
|
||||
return nil, errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
|
||||
// check if we already have a deadline
|
||||
d, ok := ctx.Deadline()
|
||||
if !ok {
|
||||
// no deadline so we create a new one
|
||||
ctx, _ = context.WithTimeout(ctx, callOpts.RequestTimeout)
|
||||
} else {
|
||||
// got a deadline so no need to setup context
|
||||
// but we need to set the timeout we pass along
|
||||
opt := WithRequestTimeout(d.Sub(time.Now()))
|
||||
opt(&callOpts)
|
||||
next, err := r.next(request, callOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// should we noop right here?
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
|
||||
return nil, errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
|
||||
default:
|
||||
}
|
||||
|
||||
call := func(i int) (Streamer, error) {
|
||||
call := func(i int) (Stream, error) {
|
||||
// call backoff first. Someone may want an initial start delay
|
||||
t, err := callOpts.Backoff(ctx, request, i)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServerError("go.micro.client", err.Error())
|
||||
return nil, errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error())
|
||||
}
|
||||
|
||||
// only sleep if greater than 0
|
||||
@@ -381,9 +385,9 @@ func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOpt
|
||||
|
||||
node, err := next()
|
||||
if err != nil && err == selector.ErrNotFound {
|
||||
return nil, errors.NotFound("go.micro.client", err.Error())
|
||||
return nil, errors.NotFound("go.micro.client", "service %s: %v", request.Service(), err.Error())
|
||||
} else if err != nil {
|
||||
return nil, errors.InternalServerError("go.micro.client", err.Error())
|
||||
return nil, errors.InternalServerError("go.micro.client", "error getting next %s node: %v", request.Service(), err.Error())
|
||||
}
|
||||
|
||||
address := node.Address
|
||||
@@ -397,14 +401,14 @@ func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOpt
|
||||
}
|
||||
|
||||
type response struct {
|
||||
stream Streamer
|
||||
stream Stream
|
||||
err error
|
||||
}
|
||||
|
||||
ch := make(chan response, callOpts.Retries)
|
||||
var grr error
|
||||
|
||||
for i := 0; i < callOpts.Retries; i++ {
|
||||
for i := 0; i <= callOpts.Retries; i++ {
|
||||
go func() {
|
||||
s, err := call(i)
|
||||
ch <- response{s, err}
|
||||
@@ -412,7 +416,7 @@ func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOpt
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.New("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()), 408)
|
||||
return nil, errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
|
||||
case rsp := <-ch:
|
||||
// if the call succeeded lets bail early
|
||||
if rsp.err == nil {
|
||||
@@ -435,49 +439,38 @@ func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOpt
|
||||
return nil, grr
|
||||
}
|
||||
|
||||
func (r *rpcClient) Publish(ctx context.Context, p Publication, opts ...PublishOption) error {
|
||||
func (r *rpcClient) Publish(ctx context.Context, msg Message, opts ...PublishOption) error {
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
if !ok {
|
||||
md = make(map[string]string)
|
||||
}
|
||||
md["Content-Type"] = p.ContentType()
|
||||
md["Content-Type"] = msg.ContentType()
|
||||
|
||||
// encode message body
|
||||
cf, err := r.newCodec(p.ContentType())
|
||||
cf, err := r.newCodec(msg.ContentType())
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
b := &buffer{bytes.NewBuffer(nil)}
|
||||
if err := cf(b).Write(&codec.Message{Type: codec.Publication}, p.Message()); err != nil {
|
||||
if err := cf(b).Write(&codec.Message{Type: codec.Publication}, msg.Payload()); err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
r.once.Do(func() {
|
||||
r.opts.Broker.Connect()
|
||||
})
|
||||
|
||||
return r.opts.Broker.Publish(p.Topic(), &broker.Message{
|
||||
return r.opts.Broker.Publish(msg.Topic(), &broker.Message{
|
||||
Header: md,
|
||||
Body: b.Bytes(),
|
||||
})
|
||||
}
|
||||
|
||||
func (r *rpcClient) NewPublication(topic string, message interface{}) Publication {
|
||||
return newRpcPublication(topic, message, r.opts.ContentType)
|
||||
func (r *rpcClient) NewMessage(topic string, message interface{}, opts ...MessageOption) Message {
|
||||
return newMessage(topic, message, r.opts.ContentType, opts...)
|
||||
}
|
||||
|
||||
func (r *rpcClient) NewProtoPublication(topic string, message interface{}) Publication {
|
||||
return newRpcPublication(topic, message, "application/octet-stream")
|
||||
}
|
||||
func (r *rpcClient) NewRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
|
||||
return newRpcRequest(service, method, request, r.opts.ContentType, reqOpts...)
|
||||
}
|
||||
|
||||
func (r *rpcClient) NewProtoRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
|
||||
return newRpcRequest(service, method, request, "application/octet-stream", reqOpts...)
|
||||
}
|
||||
|
||||
func (r *rpcClient) NewJsonRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
|
||||
return newRpcRequest(service, method, request, "application/json", reqOpts...)
|
||||
return newRequest(service, method, request, r.opts.ContentType, reqOpts...)
|
||||
}
|
||||
|
||||
func (r *rpcClient) String() string {
|
||||
|
@@ -1,16 +1,102 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/errors"
|
||||
"github.com/micro/go-micro/registry"
|
||||
"github.com/micro/go-micro/registry/mock"
|
||||
"github.com/micro/go-micro/selector"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestCallAddress(t *testing.T) {
|
||||
var called bool
|
||||
service := "test.service"
|
||||
method := "Test.Method"
|
||||
address := "10.1.10.1:8080"
|
||||
|
||||
wrap := func(cf CallFunc) CallFunc {
|
||||
return func(ctx context.Context, addr string, req Request, rsp interface{}, opts CallOptions) error {
|
||||
called = true
|
||||
|
||||
if req.Service() != service {
|
||||
return fmt.Errorf("expected service: %s got %s", service, req.Service())
|
||||
}
|
||||
|
||||
if req.Method() != method {
|
||||
return fmt.Errorf("expected service: %s got %s", method, req.Method())
|
||||
}
|
||||
|
||||
if addr != address {
|
||||
return fmt.Errorf("expected address: %s got %s", address, addr)
|
||||
}
|
||||
|
||||
// don't do the call
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
r := mock.NewRegistry()
|
||||
c := NewClient(
|
||||
Registry(r),
|
||||
WrapCall(wrap),
|
||||
)
|
||||
c.Options().Selector.Init(selector.Registry(r))
|
||||
|
||||
req := c.NewRequest(service, method, nil)
|
||||
|
||||
// test calling remote address
|
||||
if err := c.Call(context.Background(), req, nil, WithAddress(address)); err != nil {
|
||||
t.Fatal("call with address error", err)
|
||||
}
|
||||
|
||||
if !called {
|
||||
t.Fatal("wrapper not called")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCallRetry(t *testing.T) {
|
||||
service := "test.service"
|
||||
method := "Test.Method"
|
||||
address := "10.1.10.1:8080"
|
||||
|
||||
var called int
|
||||
|
||||
wrap := func(cf CallFunc) CallFunc {
|
||||
return func(ctx context.Context, addr string, req Request, rsp interface{}, opts CallOptions) error {
|
||||
called++
|
||||
if called == 1 {
|
||||
return errors.InternalServerError("test.error", "retry request")
|
||||
}
|
||||
|
||||
// don't do the call
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
r := mock.NewRegistry()
|
||||
c := NewClient(
|
||||
Registry(r),
|
||||
WrapCall(wrap),
|
||||
)
|
||||
c.Options().Selector.Init(selector.Registry(r))
|
||||
|
||||
req := c.NewRequest(service, method, nil)
|
||||
|
||||
// test calling remote address
|
||||
if err := c.Call(context.Background(), req, nil, WithAddress(address)); err != nil {
|
||||
t.Fatal("call with address error", err)
|
||||
}
|
||||
|
||||
// num calls
|
||||
if called < c.Options().CallOptions.Retries+1 {
|
||||
t.Fatal("request not retried")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallWrapper(t *testing.T) {
|
||||
var called bool
|
||||
id := "test.1"
|
||||
|
36
client/rpc_message.go
Normal file
36
client/rpc_message.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package client
|
||||
|
||||
type message struct {
|
||||
topic string
|
||||
contentType string
|
||||
payload interface{}
|
||||
}
|
||||
|
||||
func newMessage(topic string, payload interface{}, contentType string, opts ...MessageOption) Message {
|
||||
var options MessageOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
if len(options.ContentType) > 0 {
|
||||
contentType = options.ContentType
|
||||
}
|
||||
|
||||
return &message{
|
||||
payload: payload,
|
||||
topic: topic,
|
||||
contentType: contentType,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *message) ContentType() string {
|
||||
return m.contentType
|
||||
}
|
||||
|
||||
func (m *message) Topic() string {
|
||||
return m.topic
|
||||
}
|
||||
|
||||
func (m *message) Payload() interface{} {
|
||||
return m.payload
|
||||
}
|
@@ -1,27 +0,0 @@
|
||||
package client
|
||||
|
||||
type rpcPublication struct {
|
||||
topic string
|
||||
contentType string
|
||||
message interface{}
|
||||
}
|
||||
|
||||
func newRpcPublication(topic string, message interface{}, contentType string) Publication {
|
||||
return &rpcPublication{
|
||||
message: message,
|
||||
topic: topic,
|
||||
contentType: contentType,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rpcPublication) ContentType() string {
|
||||
return r.contentType
|
||||
}
|
||||
|
||||
func (r *rpcPublication) Topic() string {
|
||||
return r.topic
|
||||
}
|
||||
|
||||
func (r *rpcPublication) Message() interface{} {
|
||||
return r.message
|
||||
}
|
@@ -8,13 +8,18 @@ type rpcRequest struct {
|
||||
opts RequestOptions
|
||||
}
|
||||
|
||||
func newRpcRequest(service, method string, request interface{}, contentType string, reqOpts ...RequestOption) Request {
|
||||
func newRequest(service, method string, request interface{}, contentType string, reqOpts ...RequestOption) Request {
|
||||
var opts RequestOptions
|
||||
|
||||
for _, o := range reqOpts {
|
||||
o(&opts)
|
||||
}
|
||||
|
||||
// set the content-type specified
|
||||
if len(opts.ContentType) > 0 {
|
||||
contentType = opts.ContentType
|
||||
}
|
||||
|
||||
return &rpcRequest{
|
||||
service: service,
|
||||
method: method,
|
||||
|
23
client/rpc_request_test.go
Normal file
23
client/rpc_request_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequestOptions(t *testing.T) {
|
||||
r := newRequest("service", "method", nil, "application/json")
|
||||
if r.Service() != "service" {
|
||||
t.Fatalf("expected 'service' got %s", r.Service())
|
||||
}
|
||||
if r.Method() != "method" {
|
||||
t.Fatalf("expected 'method' got %s", r.Method())
|
||||
}
|
||||
if r.ContentType() != "application/json" {
|
||||
t.Fatalf("expected 'method' got %s", r.ContentType())
|
||||
}
|
||||
|
||||
r2 := newRequest("service", "method", nil, "application/json", WithContentType("application/protobuf"))
|
||||
if r2.ContentType() != "application/protobuf" {
|
||||
t.Fatalf("expected 'method' got %s", r2.ContentType())
|
||||
}
|
||||
}
|
@@ -1,10 +1,9 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Implements the streamer interface
|
||||
@@ -45,7 +44,6 @@ func (r *rpcStream) Send(msg interface{}) error {
|
||||
}
|
||||
|
||||
seq := r.seq
|
||||
r.seq++
|
||||
|
||||
req := request{
|
||||
Service: r.request.Service(),
|
||||
|
17
client/wrapper.go
Normal file
17
client/wrapper.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// CallFunc represents the individual call func
|
||||
type CallFunc func(ctx context.Context, address string, req Request, rsp interface{}, opts CallOptions) error
|
||||
|
||||
// CallWrapper is a low level wrapper for the CallFunc
|
||||
type CallWrapper func(CallFunc) CallFunc
|
||||
|
||||
// Wrapper wraps a client and returns a client
|
||||
type Wrapper func(Client) Client
|
||||
|
||||
// StreamWrapper wraps a Stream and returns the equivalent
|
||||
type StreamWrapper func(Stream) Stream
|
120
cmd/cmd.go
120
cmd/cmd.go
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/micro/cli"
|
||||
"github.com/micro/go-log"
|
||||
"github.com/micro/go-micro/client"
|
||||
"github.com/micro/go-micro/server"
|
||||
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
// registries
|
||||
"github.com/micro/go-micro/registry"
|
||||
"github.com/micro/go-micro/registry/consul"
|
||||
"github.com/micro/go-micro/registry/gossip"
|
||||
"github.com/micro/go-micro/registry/mdns"
|
||||
|
||||
// selectors
|
||||
@@ -71,13 +73,27 @@ var (
|
||||
Name: "client_pool_size",
|
||||
EnvVar: "MICRO_CLIENT_POOL_SIZE",
|
||||
Usage: "Sets the client connection pool size. Default: 1",
|
||||
Value: 1,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "client_pool_ttl",
|
||||
EnvVar: "MICRO_CLIENT_POOL_TTL",
|
||||
Usage: "Sets the client connection pool ttl. e.g 500ms, 5s, 1m. Default: 1m",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "register_ttl",
|
||||
EnvVar: "MICRO_REGISTER_TTL",
|
||||
Usage: "Register TTL in seconds",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "register_interval",
|
||||
EnvVar: "MICRO_REGISTER_INTERVAL",
|
||||
Usage: "Register interval in seconds",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "server",
|
||||
EnvVar: "MICRO_SERVER",
|
||||
Usage: "Server for go-micro; rpc",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "server_name",
|
||||
EnvVar: "MICRO_SERVER_NAME",
|
||||
@@ -135,11 +151,6 @@ var (
|
||||
Usage: "Selector used to pick nodes for querying",
|
||||
Value: "cache",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "server",
|
||||
EnvVar: "MICRO_SERVER",
|
||||
Usage: "Server for go-micro; rpc",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "transport",
|
||||
EnvVar: "MICRO_TRANSPORT",
|
||||
@@ -162,6 +173,7 @@ var (
|
||||
|
||||
DefaultRegistries = map[string]func(...registry.Option) registry.Registry{
|
||||
"consul": consul.NewRegistry,
|
||||
"gossip": gossip.NewRegistry,
|
||||
"mdns": mdns.NewRegistry,
|
||||
}
|
||||
|
||||
@@ -253,83 +265,75 @@ func (c *cmd) Before(ctx *cli.Context) error {
|
||||
|
||||
// Set the client
|
||||
if name := ctx.String("client"); len(name) > 0 {
|
||||
if cl, ok := c.opts.Clients[name]; ok {
|
||||
// only change if we have the client and type differs
|
||||
if cl, ok := c.opts.Clients[name]; ok && (*c.opts.Client).String() != name {
|
||||
*c.opts.Client = cl()
|
||||
}
|
||||
}
|
||||
|
||||
// Set the server
|
||||
if name := ctx.String("server"); len(name) > 0 {
|
||||
if s, ok := c.opts.Servers[name]; ok {
|
||||
// only change if we have the server and type differs
|
||||
if s, ok := c.opts.Servers[name]; ok && (*c.opts.Server).String() != name {
|
||||
*c.opts.Server = s()
|
||||
}
|
||||
}
|
||||
|
||||
// Set the broker
|
||||
if name := ctx.String("broker"); len(name) > 0 || len(ctx.String("broker_address")) > 0 {
|
||||
if len(name) == 0 {
|
||||
name = defaultBroker
|
||||
}
|
||||
|
||||
if b, ok := c.opts.Brokers[name]; ok {
|
||||
n := b(broker.Addrs(strings.Split(ctx.String("broker_address"), ",")...))
|
||||
*c.opts.Broker = n
|
||||
} else {
|
||||
if name := ctx.String("broker"); len(name) > 0 && (*c.opts.Broker).String() != name {
|
||||
b, ok := c.opts.Brokers[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("Broker %s not found", name)
|
||||
}
|
||||
|
||||
*c.opts.Broker = b()
|
||||
serverOpts = append(serverOpts, server.Broker(*c.opts.Broker))
|
||||
clientOpts = append(clientOpts, client.Broker(*c.opts.Broker))
|
||||
}
|
||||
|
||||
// Set the registry
|
||||
if name := ctx.String("registry"); len(name) > 0 || len(ctx.String("registry_address")) > 0 {
|
||||
if len(name) == 0 {
|
||||
name = defaultRegistry
|
||||
}
|
||||
|
||||
if r, ok := c.opts.Registries[name]; ok {
|
||||
n := r(registry.Addrs(strings.Split(ctx.String("registry_address"), ",")...))
|
||||
*c.opts.Registry = n
|
||||
} else {
|
||||
if name := ctx.String("registry"); len(name) > 0 && (*c.opts.Registry).String() != name {
|
||||
r, ok := c.opts.Registries[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("Registry %s not found", name)
|
||||
}
|
||||
|
||||
*c.opts.Registry = r()
|
||||
serverOpts = append(serverOpts, server.Registry(*c.opts.Registry))
|
||||
clientOpts = append(clientOpts, client.Registry(*c.opts.Registry))
|
||||
|
||||
(*c.opts.Selector).Init(selector.Registry(*c.opts.Registry))
|
||||
if err := (*c.opts.Selector).Init(selector.Registry(*c.opts.Registry)); err != nil {
|
||||
log.Fatalf("Error configuring registry: %v", err)
|
||||
}
|
||||
|
||||
clientOpts = append(clientOpts, client.Selector(*c.opts.Selector))
|
||||
|
||||
(*c.opts.Broker).Init(broker.Registry(*c.opts.Registry))
|
||||
if err := (*c.opts.Broker).Init(broker.Registry(*c.opts.Registry)); err != nil {
|
||||
log.Fatalf("Error configuring broker: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the selector
|
||||
if name := ctx.String("selector"); len(name) > 0 {
|
||||
if s, ok := c.opts.Selectors[name]; ok {
|
||||
n := s(selector.Registry(*c.opts.Registry))
|
||||
*c.opts.Selector = n
|
||||
} else {
|
||||
if name := ctx.String("selector"); len(name) > 0 && (*c.opts.Selector).String() != name {
|
||||
s, ok := c.opts.Selectors[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("Selector %s not found", name)
|
||||
}
|
||||
|
||||
*c.opts.Selector = s(selector.Registry(*c.opts.Registry))
|
||||
|
||||
// No server option here. Should there be?
|
||||
clientOpts = append(clientOpts, client.Selector(*c.opts.Selector))
|
||||
}
|
||||
|
||||
// Set the transport
|
||||
if name := ctx.String("transport"); len(name) > 0 || len(ctx.String("transport_address")) > 0 {
|
||||
if len(name) == 0 {
|
||||
name = defaultTransport
|
||||
}
|
||||
|
||||
if t, ok := c.opts.Transports[name]; ok {
|
||||
n := t(transport.Addrs(strings.Split(ctx.String("transport_address"), ",")...))
|
||||
*c.opts.Transport = n
|
||||
} else {
|
||||
if name := ctx.String("transport"); len(name) > 0 && (*c.opts.Transport).String() != name {
|
||||
t, ok := c.opts.Transports[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("Transport %s not found", name)
|
||||
}
|
||||
|
||||
*c.opts.Transport = t()
|
||||
serverOpts = append(serverOpts, server.Transport(*c.opts.Transport))
|
||||
clientOpts = append(clientOpts, client.Transport(*c.opts.Transport))
|
||||
}
|
||||
@@ -350,6 +354,24 @@ func (c *cmd) Before(ctx *cli.Context) error {
|
||||
serverOpts = append(serverOpts, server.Metadata(metadata))
|
||||
}
|
||||
|
||||
if len(ctx.String("broker_address")) > 0 {
|
||||
if err := (*c.opts.Broker).Init(broker.Addrs(strings.Split(ctx.String("broker_address"), ",")...)); err != nil {
|
||||
log.Fatalf("Error configuring broker: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ctx.String("registry_address")) > 0 {
|
||||
if err := (*c.opts.Registry).Init(registry.Addrs(strings.Split(ctx.String("registry_address"), ",")...)); err != nil {
|
||||
log.Fatalf("Error configuring registry: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ctx.String("transport_address")) > 0 {
|
||||
if err := (*c.opts.Transport).Init(transport.Addrs(strings.Split(ctx.String("transport_address"), ",")...)); err != nil {
|
||||
log.Fatalf("Error configuring transport: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ctx.String("server_name")) > 0 {
|
||||
serverOpts = append(serverOpts, server.Name(ctx.String("server_name")))
|
||||
}
|
||||
@@ -370,8 +392,12 @@ func (c *cmd) Before(ctx *cli.Context) error {
|
||||
serverOpts = append(serverOpts, server.Advertise(ctx.String("server_advertise")))
|
||||
}
|
||||
|
||||
if ttl := time.Duration(ctx.GlobalInt("register_ttl")); ttl > 0 {
|
||||
serverOpts = append(serverOpts, server.RegisterTTL(ttl*time.Second))
|
||||
}
|
||||
|
||||
// client opts
|
||||
if r := ctx.Int("client_retries"); r > 0 {
|
||||
if r := ctx.Int("client_retries"); r >= 0 {
|
||||
clientOpts = append(clientOpts, client.Retries(r))
|
||||
}
|
||||
|
||||
@@ -398,12 +424,16 @@ func (c *cmd) Before(ctx *cli.Context) error {
|
||||
// We have some command line opts for the server.
|
||||
// Lets set it up
|
||||
if len(serverOpts) > 0 {
|
||||
(*c.opts.Server).Init(serverOpts...)
|
||||
if err := (*c.opts.Server).Init(serverOpts...); err != nil {
|
||||
log.Fatalf("Error configuring server: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Use an init option?
|
||||
if len(clientOpts) > 0 {
|
||||
(*c.opts.Client).Init(clientOpts...)
|
||||
if err := (*c.opts.Client).Init(clientOpts...); err != nil {
|
||||
log.Fatalf("Error configuring client: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@@ -1,14 +1,14 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/broker"
|
||||
"github.com/micro/go-micro/client"
|
||||
"github.com/micro/go-micro/registry"
|
||||
"github.com/micro/go-micro/selector"
|
||||
"github.com/micro/go-micro/server"
|
||||
"github.com/micro/go-micro/transport"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// Package jsonrpc provides a json-rpc 1.0 codec
|
||||
package jsonrpc
|
||||
|
||||
import (
|
||||
@@ -54,7 +55,8 @@ func (j *jsonCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error {
|
||||
case codec.Response:
|
||||
return j.c.ReadHeader(m)
|
||||
case codec.Publication:
|
||||
io.Copy(j.buf, j.rwc)
|
||||
_, err := io.Copy(j.buf, j.rwc)
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("Unrecognised message type: %v", mt)
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// Protorpc provides a net/rpc proto-rpc codec. See envelope.proto for the format.
|
||||
package protorpc
|
||||
|
||||
import (
|
||||
@@ -55,7 +56,9 @@ func (c *protoCodec) Write(m *codec.Message, b interface{}) error {
|
||||
return err
|
||||
}
|
||||
if flusher, ok := c.rwc.(flusher); ok {
|
||||
err = flusher.Flush()
|
||||
if err = flusher.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case codec.Response:
|
||||
c.Lock()
|
||||
@@ -82,7 +85,9 @@ func (c *protoCodec) Write(m *codec.Message, b interface{}) error {
|
||||
return err
|
||||
}
|
||||
if flusher, ok := c.rwc.(flusher); ok {
|
||||
err = flusher.Flush()
|
||||
if err = flusher.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case codec.Publication:
|
||||
data, err := proto.Marshal(b.(proto.Message))
|
||||
@@ -127,7 +132,8 @@ func (c *protoCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error {
|
||||
m.Id = rtmp.GetSeq()
|
||||
m.Error = rtmp.GetError()
|
||||
case codec.Publication:
|
||||
io.Copy(c.buf, c.rwc)
|
||||
_, err := io.Copy(c.buf, c.rwc)
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("Unrecognised message type: %v", mt)
|
||||
}
|
||||
|
@@ -82,6 +82,36 @@ func NotFound(id, format string, a ...interface{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
// MethodNotAllowed generates a 405 error.
|
||||
func MethodNotAllowed(id, format string, a ...interface{}) error {
|
||||
return &Error{
|
||||
Id: id,
|
||||
Code: 405,
|
||||
Detail: fmt.Sprintf(format, a...),
|
||||
Status: http.StatusText(405),
|
||||
}
|
||||
}
|
||||
|
||||
// Timeout generates a 408 error.
|
||||
func Timeout(id, format string, a ...interface{}) error {
|
||||
return &Error{
|
||||
Id: id,
|
||||
Code: 408,
|
||||
Detail: fmt.Sprintf(format, a...),
|
||||
Status: http.StatusText(408),
|
||||
}
|
||||
}
|
||||
|
||||
// Conflict generates a 409 error.
|
||||
func Conflict(id, format string, a ...interface{}) error {
|
||||
return &Error{
|
||||
Id: id,
|
||||
Code: 409,
|
||||
Detail: fmt.Sprintf(format, a...),
|
||||
Status: http.StatusText(409),
|
||||
}
|
||||
}
|
||||
|
||||
// InternalServerError generates a 500 error.
|
||||
func InternalServerError(id, format string, a ...interface{}) error {
|
||||
return &Error{
|
||||
|
@@ -1,10 +1,10 @@
|
||||
package micro
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/server"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type function struct {
|
||||
@@ -23,7 +23,7 @@ func fnHandlerWrapper(f Function) server.HandlerWrapper {
|
||||
|
||||
func fnSubWrapper(f Function) server.SubscriberWrapper {
|
||||
return func(s server.SubscriberFunc) server.SubscriberFunc {
|
||||
return func(ctx context.Context, msg server.Publication) error {
|
||||
return func(ctx context.Context, msg server.Message) error {
|
||||
defer f.Done()
|
||||
return s(ctx, msg)
|
||||
}
|
||||
|
@@ -1,13 +1,12 @@
|
||||
package micro
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/registry/mock"
|
||||
proto "github.com/micro/go-micro/server/debug/proto"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestFunction(t *testing.T) {
|
||||
@@ -16,8 +15,8 @@ func TestFunction(t *testing.T) {
|
||||
|
||||
// create service
|
||||
fn := NewFunction(
|
||||
Name("test.function"),
|
||||
Registry(mock.NewRegistry()),
|
||||
Name("test.function"),
|
||||
AfterStart(func() error {
|
||||
wg.Done()
|
||||
return nil
|
||||
@@ -27,29 +26,35 @@ func TestFunction(t *testing.T) {
|
||||
// we can't test fn.Init as it parses the command line
|
||||
// fn.Init()
|
||||
|
||||
ch := make(chan error, 2)
|
||||
|
||||
go func() {
|
||||
// wait for start
|
||||
wg.Wait()
|
||||
|
||||
// test call debug
|
||||
req := fn.Client().NewRequest(
|
||||
"test.function",
|
||||
"Debug.Health",
|
||||
new(proto.HealthRequest),
|
||||
)
|
||||
|
||||
rsp := new(proto.HealthResponse)
|
||||
|
||||
err := fn.Client().Call(context.TODO(), req, rsp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if rsp.Status != "ok" {
|
||||
t.Fatalf("function response: %s", rsp.Status)
|
||||
}
|
||||
// run service
|
||||
ch <- fn.Run()
|
||||
}()
|
||||
|
||||
// run service
|
||||
fn.Run()
|
||||
// wait for start
|
||||
wg.Wait()
|
||||
|
||||
// test call debug
|
||||
req := fn.Client().NewRequest(
|
||||
"test.function",
|
||||
"Debug.Health",
|
||||
new(proto.HealthRequest),
|
||||
)
|
||||
|
||||
rsp := new(proto.HealthResponse)
|
||||
|
||||
err := fn.Client().Call(context.TODO(), req, rsp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if rsp.Status != "ok" {
|
||||
t.Fatalf("function response: %s", rsp.Status)
|
||||
}
|
||||
|
||||
if err := <-ch; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@@ -2,10 +2,10 @@
|
||||
package micro
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/client"
|
||||
"github.com/micro/go-micro/server"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type serviceKey struct{}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"context"
|
||||
)
|
||||
|
||||
type metaKey struct{}
|
||||
|
@@ -1,9 +1,8 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestMetadataContext(t *testing.T) {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package micro
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/micro/cli"
|
||||
@@ -11,8 +12,6 @@ import (
|
||||
"github.com/micro/go-micro/selector"
|
||||
"github.com/micro/go-micro/server"
|
||||
"github.com/micro/go-micro/transport"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
@@ -101,6 +100,8 @@ func Registry(r registry.Registry) Option {
|
||||
o.Server.Init(server.Registry(r))
|
||||
// Update Selector
|
||||
o.Client.Options().Selector.Init(selector.Registry(r))
|
||||
// Update Broker
|
||||
o.Broker.Init(broker.Registry(r))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,6 +174,7 @@ func RegisterInterval(t time.Duration) Option {
|
||||
|
||||
// WrapClient is a convenience method for wrapping a Client with
|
||||
// some middleware component. A list of wrappers can be provided.
|
||||
// Wrappers are applied in reverse order so the last is executed first.
|
||||
func WrapClient(w ...client.Wrapper) Option {
|
||||
return func(o *Options) {
|
||||
// apply in reverse
|
||||
|
@@ -1,8 +1,9 @@
|
||||
package micro
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/client"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type publisher struct {
|
||||
@@ -11,5 +12,5 @@ type publisher struct {
|
||||
}
|
||||
|
||||
func (p *publisher) Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error {
|
||||
return p.c.Publish(ctx, p.c.NewPublication(p.topic, msg))
|
||||
return p.c.Publish(ctx, p.c.NewMessage(p.topic, msg))
|
||||
}
|
||||
|
@@ -1,9 +1,11 @@
|
||||
// Package consul provides a consul based registry and is the default discovery system
|
||||
package consul
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
// NewRegistry returns a new consul registry
|
||||
func NewRegistry(opts ...registry.Option) registry.Registry {
|
||||
return registry.NewRegistry(opts...)
|
||||
}
|
||||
|
@@ -1,11 +1,23 @@
|
||||
package consul
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
consul "github.com/hashicorp/consul/api"
|
||||
"github.com/micro/go-micro/registry"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Connect specifies services should be registered as Consul Connect services
|
||||
func Connect() registry.Option {
|
||||
return func(o *registry.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, "consul_connect", true)
|
||||
}
|
||||
}
|
||||
|
||||
func Config(c *consul.Config) registry.Option {
|
||||
return func(o *registry.Options) {
|
||||
if o.Context == nil {
|
||||
@@ -14,3 +26,56 @@ func Config(c *consul.Config) registry.Option {
|
||||
o.Context = context.WithValue(o.Context, "consul_config", c)
|
||||
}
|
||||
}
|
||||
|
||||
// AllowStale sets whether any Consul server (non-leader) can service
|
||||
// a read. This allows for lower latency and higher throughput
|
||||
// at the cost of potentially stale data.
|
||||
// Works similar to Consul DNS Config option [1].
|
||||
// Defaults to true.
|
||||
//
|
||||
// [1] https://www.consul.io/docs/agent/options.html#allow_stale
|
||||
//
|
||||
func AllowStale(v bool) registry.Option {
|
||||
return func(o *registry.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, "consul_allow_stale", v)
|
||||
}
|
||||
}
|
||||
|
||||
// QueryOptions specifies the QueryOptions to be used when calling
|
||||
// Consul. See `Consul API` for more information [1].
|
||||
//
|
||||
// [1] https://godoc.org/github.com/hashicorp/consul/api#QueryOptions
|
||||
//
|
||||
func QueryOptions(q *consul.QueryOptions) registry.Option {
|
||||
return func(o *registry.Options) {
|
||||
if q == nil {
|
||||
return
|
||||
}
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, "consul_query_options", q)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// TCPCheck will tell the service provider to check the service address
|
||||
// and port every `t` interval. It will enabled only if `t` is greater than 0.
|
||||
// See `TCP + Interval` for more information [1].
|
||||
//
|
||||
// [1] https://www.consul.io/docs/agent/checks.html
|
||||
//
|
||||
func TCPCheck(t time.Duration) registry.Option {
|
||||
return func(o *registry.Options) {
|
||||
if t <= time.Duration(0) {
|
||||
return
|
||||
}
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, "consul_tcp_check", t)
|
||||
}
|
||||
}
|
||||
|
@@ -17,10 +17,30 @@ import (
|
||||
type consulRegistry struct {
|
||||
Address string
|
||||
Client *consul.Client
|
||||
Options Options
|
||||
opts Options
|
||||
|
||||
// connect enabled
|
||||
connect bool
|
||||
|
||||
queryOptions *consul.QueryOptions
|
||||
|
||||
sync.Mutex
|
||||
register map[string]uint64
|
||||
// lastChecked tracks when a node was last checked as existing in Consul
|
||||
lastChecked map[string]time.Time
|
||||
}
|
||||
|
||||
func getDeregisterTTL(t time.Duration) time.Duration {
|
||||
// splay slightly for the watcher?
|
||||
splay := time.Second * 5
|
||||
deregTTL := t + splay
|
||||
|
||||
// consul has a minimum timeout on deregistration of 1 minute.
|
||||
if t < time.Minute {
|
||||
deregTTL = time.Minute + splay
|
||||
}
|
||||
|
||||
return deregTTL
|
||||
}
|
||||
|
||||
func newTransport(config *tls.Config) *http.Transport {
|
||||
@@ -45,27 +65,39 @@ func newTransport(config *tls.Config) *http.Transport {
|
||||
return t
|
||||
}
|
||||
|
||||
func newConsulRegistry(opts ...Option) Registry {
|
||||
var options Options
|
||||
func configure(c *consulRegistry, opts ...Option) {
|
||||
// set opts
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
o(&c.opts)
|
||||
}
|
||||
|
||||
// use default config
|
||||
config := consul.DefaultConfig()
|
||||
if options.Context != nil {
|
||||
|
||||
if c.opts.Context != nil {
|
||||
// Use the consul config passed in the options, if available
|
||||
if c, ok := options.Context.Value("consul_config").(*consul.Config); ok {
|
||||
config = c
|
||||
if co, ok := c.opts.Context.Value("consul_config").(*consul.Config); ok {
|
||||
config = co
|
||||
}
|
||||
if cn, ok := c.opts.Context.Value("consul_connect").(bool); ok {
|
||||
c.connect = cn
|
||||
}
|
||||
|
||||
// Use the consul query options passed in the options, if available
|
||||
if qo, ok := c.opts.Context.Value("consul_query_options").(*consul.QueryOptions); ok && qo != nil {
|
||||
c.queryOptions = qo
|
||||
}
|
||||
if as, ok := c.opts.Context.Value("consul_allow_stale").(bool); ok {
|
||||
c.queryOptions.AllowStale = as
|
||||
}
|
||||
}
|
||||
|
||||
// check if there are any addrs
|
||||
if len(options.Addrs) > 0 {
|
||||
addr, port, err := net.SplitHostPort(options.Addrs[0])
|
||||
if len(c.opts.Addrs) > 0 {
|
||||
addr, port, err := net.SplitHostPort(c.opts.Addrs[0])
|
||||
if ae, ok := err.(*net.AddrError); ok && ae.Err == "missing port in address" {
|
||||
port = "8500"
|
||||
addr = options.Addrs[0]
|
||||
addr = c.opts.Addrs[0]
|
||||
config.Address = fmt.Sprintf("%s:%s", addr, port)
|
||||
} else if err == nil {
|
||||
config.Address = fmt.Sprintf("%s:%s", addr, port)
|
||||
@@ -73,42 +105,56 @@ func newConsulRegistry(opts ...Option) Registry {
|
||||
}
|
||||
|
||||
// requires secure connection?
|
||||
if options.Secure || options.TLSConfig != nil {
|
||||
if c.opts.Secure || c.opts.TLSConfig != nil {
|
||||
if config.HttpClient == nil {
|
||||
config.HttpClient = new(http.Client)
|
||||
}
|
||||
|
||||
config.Scheme = "https"
|
||||
// We're going to support InsecureSkipVerify
|
||||
config.HttpClient.Transport = newTransport(options.TLSConfig)
|
||||
config.HttpClient.Transport = newTransport(c.opts.TLSConfig)
|
||||
}
|
||||
|
||||
// set timeout
|
||||
if c.opts.Timeout > 0 {
|
||||
config.HttpClient.Timeout = c.opts.Timeout
|
||||
}
|
||||
|
||||
// create the client
|
||||
client, _ := consul.NewClient(config)
|
||||
|
||||
// set timeout
|
||||
if options.Timeout > 0 {
|
||||
config.HttpClient.Timeout = options.Timeout
|
||||
}
|
||||
// set address/client
|
||||
c.Address = config.Address
|
||||
c.Client = client
|
||||
}
|
||||
|
||||
func newConsulRegistry(opts ...Option) Registry {
|
||||
cr := &consulRegistry{
|
||||
Address: config.Address,
|
||||
Client: client,
|
||||
Options: options,
|
||||
register: make(map[string]uint64),
|
||||
opts: Options{},
|
||||
register: make(map[string]uint64),
|
||||
lastChecked: make(map[string]time.Time),
|
||||
queryOptions: &consul.QueryOptions{
|
||||
AllowStale: true,
|
||||
},
|
||||
}
|
||||
|
||||
configure(cr, opts...)
|
||||
return cr
|
||||
}
|
||||
|
||||
func (c *consulRegistry) Init(opts ...Option) error {
|
||||
configure(c, opts...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *consulRegistry) Deregister(s *Service) error {
|
||||
if len(s.Nodes) == 0 {
|
||||
return errors.New("Require at least one node")
|
||||
}
|
||||
|
||||
// delete our hash of the service
|
||||
// delete our hash and time check of the service
|
||||
c.Lock()
|
||||
delete(c.register, s.Name)
|
||||
delete(c.lastChecked, s.Name)
|
||||
c.Unlock()
|
||||
|
||||
node := s.Nodes[0]
|
||||
@@ -120,11 +166,21 @@ func (c *consulRegistry) Register(s *Service, opts ...RegisterOption) error {
|
||||
return errors.New("Require at least one node")
|
||||
}
|
||||
|
||||
var regTCPCheck bool
|
||||
var regInterval time.Duration
|
||||
|
||||
var options RegisterOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
if c.opts.Context != nil {
|
||||
if tcpCheckInterval, ok := c.opts.Context.Value("consul_tcp_check").(time.Duration); ok {
|
||||
regTCPCheck = true
|
||||
regInterval = tcpCheckInterval
|
||||
}
|
||||
}
|
||||
|
||||
// create hash of service; uint64
|
||||
h, err := hash.Hash(s, nil)
|
||||
if err != nil {
|
||||
@@ -134,17 +190,33 @@ func (c *consulRegistry) Register(s *Service, opts ...RegisterOption) error {
|
||||
// use first node
|
||||
node := s.Nodes[0]
|
||||
|
||||
// get existing hash
|
||||
// get existing hash and last checked time
|
||||
c.Lock()
|
||||
v, ok := c.register[s.Name]
|
||||
lastChecked := c.lastChecked[s.Name]
|
||||
c.Unlock()
|
||||
|
||||
// if it's already registered and matches then just pass the check
|
||||
if ok && v == h {
|
||||
// if the err is nil we're all good, bail out
|
||||
// if not, we don't know what the state is, so full re-register
|
||||
if err := c.Client.Agent().PassTTL("service:"+node.Id, ""); err == nil {
|
||||
return nil
|
||||
if options.TTL == time.Duration(0) {
|
||||
// ensure that our service hasn't been deregistered by Consul
|
||||
if time.Since(lastChecked) <= getDeregisterTTL(regInterval) {
|
||||
return nil
|
||||
}
|
||||
services, _, err := c.Client.Health().Checks(s.Name, c.queryOptions)
|
||||
if err == nil {
|
||||
for _, v := range services {
|
||||
if v.ServiceID == node.Id {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if the err is nil we're all good, bail out
|
||||
// if not, we don't know what the state is, so full re-register
|
||||
if err := c.Client.Agent().PassTTL("service:"+node.Id, ""); err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,37 +227,50 @@ func (c *consulRegistry) Register(s *Service, opts ...RegisterOption) error {
|
||||
|
||||
var check *consul.AgentServiceCheck
|
||||
|
||||
// if the TTL is greater than 0 create an associated check
|
||||
if options.TTL > time.Duration(0) {
|
||||
// splay slightly for the watcher?
|
||||
splay := time.Second * 5
|
||||
deregTTL := options.TTL + splay
|
||||
// consul has a minimum timeout on deregistration of 1 minute.
|
||||
if options.TTL < time.Minute {
|
||||
deregTTL = time.Minute + splay
|
||||
}
|
||||
if regTCPCheck {
|
||||
deregTTL := getDeregisterTTL(regInterval)
|
||||
|
||||
check = &consul.AgentServiceCheck{
|
||||
TTL: fmt.Sprintf("%v", options.TTL),
|
||||
TCP: fmt.Sprintf("%s:%d", node.Address, node.Port),
|
||||
Interval: fmt.Sprintf("%v", regInterval),
|
||||
DeregisterCriticalServiceAfter: fmt.Sprintf("%v", deregTTL),
|
||||
}
|
||||
|
||||
// if the TTL is greater than 0 create an associated check
|
||||
} else if options.TTL > time.Duration(0) {
|
||||
deregTTL := getDeregisterTTL(options.TTL)
|
||||
|
||||
check = &consul.AgentServiceCheck{
|
||||
TTL: fmt.Sprintf("%v", options.TTL),
|
||||
DeregisterCriticalServiceAfter: fmt.Sprintf("%v", deregTTL),
|
||||
}
|
||||
}
|
||||
|
||||
// register the service
|
||||
if err := c.Client.Agent().ServiceRegister(&consul.AgentServiceRegistration{
|
||||
asr := &consul.AgentServiceRegistration{
|
||||
ID: node.Id,
|
||||
Name: s.Name,
|
||||
Tags: tags,
|
||||
Port: node.Port,
|
||||
Address: node.Address,
|
||||
Check: check,
|
||||
}); err != nil {
|
||||
}
|
||||
|
||||
// Specify consul connect
|
||||
if c.connect {
|
||||
asr.Connect = &consul.AgentServiceConnect{
|
||||
Native: true,
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.Client.Agent().ServiceRegister(asr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// save our hash of the service
|
||||
// save our hash and time check of the service
|
||||
c.Lock()
|
||||
c.register[s.Name] = h
|
||||
c.lastChecked[s.Name] = time.Now()
|
||||
c.Unlock()
|
||||
|
||||
// if the TTL is 0 we don't mess with the checks
|
||||
@@ -198,7 +283,15 @@ func (c *consulRegistry) Register(s *Service, opts ...RegisterOption) error {
|
||||
}
|
||||
|
||||
func (c *consulRegistry) GetService(name string) ([]*Service, error) {
|
||||
rsp, _, err := c.Client.Health().Service(name, "", false, nil)
|
||||
var rsp []*consul.ServiceEntry
|
||||
var err error
|
||||
|
||||
// if we're connect enabled only get connect services
|
||||
if c.connect {
|
||||
rsp, _, err = c.Client.Health().Connect(name, "", false, c.queryOptions)
|
||||
} else {
|
||||
rsp, _, err = c.Client.Health().Service(name, "", false, c.queryOptions)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -211,18 +304,18 @@ func (c *consulRegistry) GetService(name string) ([]*Service, error) {
|
||||
}
|
||||
|
||||
// version is now a tag
|
||||
version, found := decodeVersion(s.Service.Tags)
|
||||
version, _ := decodeVersion(s.Service.Tags)
|
||||
// service ID is now the node id
|
||||
id := s.Service.ID
|
||||
// key is always the version
|
||||
key := version
|
||||
|
||||
// address is service address
|
||||
address := s.Service.Address
|
||||
|
||||
// if we can't get the version we bail
|
||||
// use old the old ways
|
||||
if !found {
|
||||
continue
|
||||
// use node address
|
||||
if len(address) == 0 {
|
||||
address = s.Node.Address
|
||||
}
|
||||
|
||||
svc, ok := serviceMap[key]
|
||||
@@ -236,6 +329,7 @@ func (c *consulRegistry) GetService(name string) ([]*Service, error) {
|
||||
}
|
||||
|
||||
var del bool
|
||||
|
||||
for _, check := range s.Checks {
|
||||
// delete the node if the status is critical
|
||||
if check.Status == "critical" {
|
||||
@@ -265,7 +359,7 @@ func (c *consulRegistry) GetService(name string) ([]*Service, error) {
|
||||
}
|
||||
|
||||
func (c *consulRegistry) ListServices() ([]*Service, error) {
|
||||
rsp, _, err := c.Client.Catalog().Services(nil)
|
||||
rsp, _, err := c.Client.Catalog().Services(c.queryOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -279,10 +373,14 @@ func (c *consulRegistry) ListServices() ([]*Service, error) {
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func (c *consulRegistry) Watch() (Watcher, error) {
|
||||
return newConsulWatcher(c)
|
||||
func (c *consulRegistry) Watch(opts ...WatchOption) (Watcher, error) {
|
||||
return newConsulWatcher(c, opts...)
|
||||
}
|
||||
|
||||
func (c *consulRegistry) String() string {
|
||||
return "consul"
|
||||
}
|
||||
|
||||
func (c *consulRegistry) Options() Options {
|
||||
return c.opts
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
consul "github.com/hashicorp/consul/api"
|
||||
)
|
||||
@@ -53,9 +54,14 @@ func newConsulTestRegistry(r *mockRegistry) (*consulRegistry, func()) {
|
||||
go newMockServer(r, l)
|
||||
|
||||
return &consulRegistry{
|
||||
Address: cfg.Address,
|
||||
Client: cl,
|
||||
register: make(map[string]uint64),
|
||||
Address: cfg.Address,
|
||||
Client: cl,
|
||||
opts: Options{},
|
||||
register: make(map[string]uint64),
|
||||
lastChecked: make(map[string]time.Time),
|
||||
queryOptions: &consul.QueryOptions{
|
||||
AllowStale: true,
|
||||
},
|
||||
}, func() {
|
||||
l.Close()
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
type consulWatcher struct {
|
||||
r *consulRegistry
|
||||
wo WatchOptions
|
||||
wp *watch.Plan
|
||||
watchers map[string]*watch.Plan
|
||||
|
||||
@@ -20,9 +21,15 @@ type consulWatcher struct {
|
||||
services map[string][]*Service
|
||||
}
|
||||
|
||||
func newConsulWatcher(cr *consulRegistry) (Watcher, error) {
|
||||
func newConsulWatcher(cr *consulRegistry, opts ...WatchOption) (Watcher, error) {
|
||||
var wo WatchOptions
|
||||
for _, o := range opts {
|
||||
o(&wo)
|
||||
}
|
||||
|
||||
cw := &consulWatcher{
|
||||
r: cr,
|
||||
wo: wo,
|
||||
exit: make(chan bool),
|
||||
next: make(chan *Result, 10),
|
||||
watchers: make(map[string]*watch.Plan),
|
||||
@@ -53,7 +60,7 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) {
|
||||
for _, e := range entries {
|
||||
serviceName = e.Service.Service
|
||||
// version is now a tag
|
||||
version, found := decodeVersion(e.Service.Tags)
|
||||
version, _ := decodeVersion(e.Service.Tags)
|
||||
// service ID is now the node id
|
||||
id := e.Service.ID
|
||||
// key is always the version
|
||||
@@ -61,9 +68,9 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) {
|
||||
// address is service address
|
||||
address := e.Service.Address
|
||||
|
||||
// if we can't get the version we bail
|
||||
if !found {
|
||||
continue
|
||||
// use node address
|
||||
if len(address) == 0 {
|
||||
address = e.Node.Address
|
||||
}
|
||||
|
||||
svc, ok := serviceMap[key]
|
||||
@@ -185,6 +192,12 @@ func (cw *consulWatcher) handle(idx uint64, data interface{}) {
|
||||
|
||||
// add new watchers
|
||||
for service, _ := range services {
|
||||
// Filter on watch options
|
||||
// wo.Service: Only watch services we care about
|
||||
if len(cw.wo.Service) > 0 && service != cw.wo.Service {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := cw.watchers[service]; ok {
|
||||
continue
|
||||
}
|
||||
|
24
registry/gossip/README.md
Normal file
24
registry/gossip/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Gossip Registry
|
||||
|
||||
Gossip is a zero dependency registry which uses hashicorp/memberlist to broadcast registry information
|
||||
via the SWIM protocol.
|
||||
|
||||
## Usage
|
||||
|
||||
Start with the registry flag or env var
|
||||
|
||||
```bash
|
||||
MICRO_REGISTRY=gossip go run service.go
|
||||
```
|
||||
|
||||
On startup you'll see something like
|
||||
|
||||
```bash
|
||||
2018/12/06 18:17:48 Registry Listening on 192.168.1.65:56390
|
||||
```
|
||||
|
||||
To join this gossip ring set the registry address using flag or env var
|
||||
|
||||
```bash
|
||||
MICRO_REGISTRY_ADDRESS= 192.168.1.65:56390
|
||||
```
|
565
registry/gossip/gossip.go
Normal file
565
registry/gossip/gossip.go
Normal file
@@ -0,0 +1,565 @@
|
||||
// Package Gossip provides a gossip registry based on hashicorp/memberlist
|
||||
package gossip
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hashicorp/memberlist"
|
||||
"github.com/micro/go-log"
|
||||
"github.com/micro/go-micro/registry"
|
||||
pb "github.com/micro/go-micro/registry/gossip/proto"
|
||||
"github.com/mitchellh/hashstructure"
|
||||
)
|
||||
|
||||
const (
|
||||
addAction = "update"
|
||||
delAction = "delete"
|
||||
syncAction = "sync"
|
||||
)
|
||||
|
||||
type broadcast struct {
|
||||
update *pb.Update
|
||||
notify chan<- struct{}
|
||||
}
|
||||
|
||||
type delegate struct {
|
||||
queue *memberlist.TransmitLimitedQueue
|
||||
updates chan *update
|
||||
}
|
||||
|
||||
type gossipRegistry struct {
|
||||
queue *memberlist.TransmitLimitedQueue
|
||||
updates chan *update
|
||||
options registry.Options
|
||||
member *memberlist.Memberlist
|
||||
interval time.Duration
|
||||
|
||||
sync.RWMutex
|
||||
services map[string][]*registry.Service
|
||||
|
||||
s sync.RWMutex
|
||||
watchers map[string]chan *registry.Result
|
||||
}
|
||||
|
||||
type update struct {
|
||||
Update *pb.Update
|
||||
Service *registry.Service
|
||||
sync chan *registry.Service
|
||||
}
|
||||
|
||||
var (
|
||||
// You should change this if using secure
|
||||
DefaultSecret = []byte("gossip")
|
||||
ExpiryTick = time.Second * 5
|
||||
)
|
||||
|
||||
func configure(g *gossipRegistry, opts ...registry.Option) error {
|
||||
// loop through address list and get valid entries
|
||||
addrs := func(curAddrs []string) []string {
|
||||
var newAddrs []string
|
||||
for _, addr := range curAddrs {
|
||||
if trimAddr := strings.TrimSpace(addr); len(trimAddr) > 0 {
|
||||
newAddrs = append(newAddrs, trimAddr)
|
||||
}
|
||||
}
|
||||
return newAddrs
|
||||
}
|
||||
|
||||
// current address list
|
||||
curAddrs := addrs(g.options.Addrs)
|
||||
|
||||
// parse options
|
||||
for _, o := range opts {
|
||||
o(&g.options)
|
||||
}
|
||||
|
||||
// new address list
|
||||
newAddrs := addrs(g.options.Addrs)
|
||||
|
||||
// no new nodes and existing member. no configure
|
||||
if (len(newAddrs) == len(curAddrs)) && g.member != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// shutdown old member
|
||||
if g.member != nil {
|
||||
g.member.Shutdown()
|
||||
}
|
||||
|
||||
// replace addresses
|
||||
curAddrs = newAddrs
|
||||
|
||||
// create a queue
|
||||
queue := &memberlist.TransmitLimitedQueue{
|
||||
NumNodes: func() int {
|
||||
return len(curAddrs)
|
||||
},
|
||||
RetransmitMult: 3,
|
||||
}
|
||||
|
||||
// machine hostname
|
||||
hostname, _ := os.Hostname()
|
||||
|
||||
// create a new default config
|
||||
c := memberlist.DefaultLocalConfig()
|
||||
|
||||
// set bind to random port
|
||||
c.BindPort = 0
|
||||
|
||||
// set the name
|
||||
c.Name = strings.Join([]string{"micro", hostname, uuid.New().String()}, "-")
|
||||
|
||||
// set the delegate
|
||||
c.Delegate = &delegate{
|
||||
updates: g.updates,
|
||||
queue: queue,
|
||||
}
|
||||
|
||||
// log to dev null
|
||||
c.LogOutput = ioutil.Discard
|
||||
|
||||
// set a secret key if secure
|
||||
if g.options.Secure {
|
||||
k, ok := g.options.Context.Value(contextSecretKey{}).([]byte)
|
||||
if !ok {
|
||||
// use the default secret
|
||||
k = DefaultSecret
|
||||
}
|
||||
c.SecretKey = k
|
||||
}
|
||||
|
||||
// TODO: set advertise addr to advertise behind nat
|
||||
|
||||
// create the memberlist
|
||||
m, err := memberlist.Create(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// join the memberlist
|
||||
if len(curAddrs) > 0 {
|
||||
_, err := m.Join(curAddrs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// set internals
|
||||
g.queue = queue
|
||||
g.member = m
|
||||
g.interval = c.GossipInterval
|
||||
|
||||
log.Logf("Registry Listening on %s", m.LocalNode().Address())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *broadcast) Invalidates(other memberlist.Broadcast) bool {
|
||||
up := new(pb.Update)
|
||||
if err := proto.Unmarshal(other.Message(), up); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// ids do not match
|
||||
if b.update.Id == up.Id {
|
||||
return false
|
||||
}
|
||||
|
||||
// timestamps do not match
|
||||
if b.update.Timestamp != up.Timestamp {
|
||||
return false
|
||||
}
|
||||
|
||||
// type does not match
|
||||
if b.update.Type != up.Type {
|
||||
return false
|
||||
}
|
||||
|
||||
// invalidates
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *broadcast) Message() []byte {
|
||||
up, err := proto.Marshal(b.update)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return up
|
||||
}
|
||||
|
||||
func (b *broadcast) Finished() {
|
||||
if b.notify != nil {
|
||||
close(b.notify)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *delegate) NodeMeta(limit int) []byte {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
func (d *delegate) NotifyMsg(b []byte) {
|
||||
if len(b) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
up := new(pb.Update)
|
||||
if err := proto.Unmarshal(b, up); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// only process service action
|
||||
if up.Type != "service" {
|
||||
return
|
||||
}
|
||||
|
||||
var service *registry.Service
|
||||
|
||||
switch up.Metadata["Content-Type"] {
|
||||
case "application/json":
|
||||
if err := json.Unmarshal(up.Data, &service); err != nil {
|
||||
return
|
||||
}
|
||||
// no other content type
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
// send update
|
||||
d.updates <- &update{
|
||||
Update: up,
|
||||
Service: service,
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (d *delegate) GetBroadcasts(overhead, limit int) [][]byte {
|
||||
return d.queue.GetBroadcasts(overhead, limit)
|
||||
}
|
||||
|
||||
func (d *delegate) LocalState(join bool) []byte {
|
||||
if !join {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
syncCh := make(chan *registry.Service, 1)
|
||||
services := map[string][]*registry.Service{}
|
||||
|
||||
d.updates <- &update{
|
||||
Update: &pb.Update{
|
||||
Action: syncAction,
|
||||
},
|
||||
sync: syncCh,
|
||||
}
|
||||
|
||||
for srv := range syncCh {
|
||||
services[srv.Name] = append(services[srv.Name], srv)
|
||||
}
|
||||
|
||||
b, _ := json.Marshal(services)
|
||||
return b
|
||||
}
|
||||
|
||||
func (d *delegate) MergeRemoteState(buf []byte, join bool) {
|
||||
if len(buf) == 0 {
|
||||
return
|
||||
}
|
||||
if !join {
|
||||
return
|
||||
}
|
||||
|
||||
var services map[string][]*registry.Service
|
||||
if err := json.Unmarshal(buf, &services); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, service := range services {
|
||||
for _, srv := range service {
|
||||
d.updates <- &update{
|
||||
Update: &pb.Update{Action: addAction},
|
||||
Service: srv,
|
||||
sync: nil,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *gossipRegistry) publish(action string, services []*registry.Service) {
|
||||
g.s.RLock()
|
||||
for _, sub := range g.watchers {
|
||||
go func(sub chan *registry.Result) {
|
||||
for _, service := range services {
|
||||
sub <- ®istry.Result{Action: action, Service: service}
|
||||
}
|
||||
}(sub)
|
||||
}
|
||||
g.s.RUnlock()
|
||||
}
|
||||
|
||||
func (g *gossipRegistry) subscribe() (chan *registry.Result, chan bool) {
|
||||
next := make(chan *registry.Result, 10)
|
||||
exit := make(chan bool)
|
||||
|
||||
id := uuid.New().String()
|
||||
|
||||
g.s.Lock()
|
||||
g.watchers[id] = next
|
||||
g.s.Unlock()
|
||||
|
||||
go func() {
|
||||
<-exit
|
||||
g.s.Lock()
|
||||
delete(g.watchers, id)
|
||||
close(next)
|
||||
g.s.Unlock()
|
||||
}()
|
||||
|
||||
return next, exit
|
||||
}
|
||||
|
||||
func (g *gossipRegistry) run() {
|
||||
var mtx sync.Mutex
|
||||
updates := map[uint64]*update{}
|
||||
|
||||
// expiry loop
|
||||
go func() {
|
||||
t := time.NewTicker(ExpiryTick)
|
||||
defer t.Stop()
|
||||
|
||||
for _ = range t.C {
|
||||
now := uint64(time.Now().UnixNano())
|
||||
|
||||
mtx.Lock()
|
||||
|
||||
// process all the updates
|
||||
for k, v := range updates {
|
||||
// check if expiry time has passed
|
||||
if d := (v.Update.Timestamp + v.Update.Expires); d < now {
|
||||
// delete from records
|
||||
delete(updates, k)
|
||||
// set to delete
|
||||
v.Update.Action = delAction
|
||||
// fire a new update
|
||||
g.updates <- v
|
||||
}
|
||||
}
|
||||
|
||||
mtx.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
// process the updates
|
||||
for u := range g.updates {
|
||||
switch u.Update.Action {
|
||||
case addAction:
|
||||
g.Lock()
|
||||
if service, ok := g.services[u.Service.Name]; !ok {
|
||||
g.services[u.Service.Name] = []*registry.Service{u.Service}
|
||||
|
||||
} else {
|
||||
g.services[u.Service.Name] = addServices(service, []*registry.Service{u.Service})
|
||||
}
|
||||
g.Unlock()
|
||||
|
||||
// publish update to watchers
|
||||
go g.publish(addAction, []*registry.Service{u.Service})
|
||||
|
||||
// we need to expire the node at some point in the future
|
||||
if u.Update.Expires > 0 {
|
||||
// create a hash of this service
|
||||
if hash, err := hashstructure.Hash(u.Service, nil); err == nil {
|
||||
mtx.Lock()
|
||||
updates[hash] = u
|
||||
mtx.Unlock()
|
||||
}
|
||||
}
|
||||
case delAction:
|
||||
g.Lock()
|
||||
if service, ok := g.services[u.Service.Name]; ok {
|
||||
if services := delServices(service, []*registry.Service{u.Service}); len(services) == 0 {
|
||||
delete(g.services, u.Service.Name)
|
||||
} else {
|
||||
g.services[u.Service.Name] = services
|
||||
}
|
||||
}
|
||||
g.Unlock()
|
||||
|
||||
// publish update to watchers
|
||||
go g.publish(delAction, []*registry.Service{u.Service})
|
||||
|
||||
// delete from expiry checks
|
||||
if hash, err := hashstructure.Hash(u.Service, nil); err == nil {
|
||||
mtx.Lock()
|
||||
delete(updates, hash)
|
||||
mtx.Unlock()
|
||||
}
|
||||
case syncAction:
|
||||
// no sync channel provided
|
||||
if u.sync == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
g.RLock()
|
||||
|
||||
// push all services through the sync chan
|
||||
for _, service := range g.services {
|
||||
for _, srv := range service {
|
||||
u.sync <- srv
|
||||
}
|
||||
|
||||
// publish to watchers
|
||||
go g.publish(addAction, service)
|
||||
}
|
||||
|
||||
g.RUnlock()
|
||||
|
||||
// close the sync chan
|
||||
close(u.sync)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *gossipRegistry) Init(opts ...registry.Option) error {
|
||||
return configure(g, opts...)
|
||||
}
|
||||
|
||||
func (g *gossipRegistry) Options() registry.Options {
|
||||
return g.options
|
||||
}
|
||||
|
||||
func (g *gossipRegistry) Register(s *registry.Service, opts ...registry.RegisterOption) error {
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.Lock()
|
||||
if service, ok := g.services[s.Name]; !ok {
|
||||
g.services[s.Name] = []*registry.Service{s}
|
||||
} else {
|
||||
g.services[s.Name] = addServices(service, []*registry.Service{s})
|
||||
}
|
||||
g.Unlock()
|
||||
|
||||
var options registry.RegisterOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
up := &pb.Update{
|
||||
Id: uuid.New().String(),
|
||||
Timestamp: uint64(time.Now().UnixNano()),
|
||||
Expires: uint64(options.TTL.Nanoseconds()),
|
||||
Action: "update",
|
||||
Type: "service",
|
||||
Metadata: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
Data: b,
|
||||
}
|
||||
|
||||
g.queue.QueueBroadcast(&broadcast{
|
||||
update: up,
|
||||
notify: nil,
|
||||
})
|
||||
|
||||
// wait
|
||||
<-time.After(g.interval * 2)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *gossipRegistry) Deregister(s *registry.Service) error {
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.Lock()
|
||||
if service, ok := g.services[s.Name]; ok {
|
||||
if services := delServices(service, []*registry.Service{s}); len(services) == 0 {
|
||||
delete(g.services, s.Name)
|
||||
} else {
|
||||
g.services[s.Name] = services
|
||||
}
|
||||
}
|
||||
g.Unlock()
|
||||
|
||||
up := &pb.Update{
|
||||
Id: uuid.New().String(),
|
||||
Timestamp: uint64(time.Now().UnixNano()),
|
||||
Action: "delete",
|
||||
Type: "service",
|
||||
Metadata: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
Data: b,
|
||||
}
|
||||
|
||||
g.queue.QueueBroadcast(&broadcast{
|
||||
update: up,
|
||||
notify: nil,
|
||||
})
|
||||
|
||||
// wait
|
||||
<-time.After(g.interval * 2)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *gossipRegistry) GetService(name string) ([]*registry.Service, error) {
|
||||
g.RLock()
|
||||
service, ok := g.services[name]
|
||||
g.RUnlock()
|
||||
if !ok {
|
||||
return nil, registry.ErrNotFound
|
||||
}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (g *gossipRegistry) ListServices() ([]*registry.Service, error) {
|
||||
var services []*registry.Service
|
||||
g.RLock()
|
||||
for _, service := range g.services {
|
||||
services = append(services, service...)
|
||||
}
|
||||
g.RUnlock()
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func (g *gossipRegistry) Watch(opts ...registry.WatchOption) (registry.Watcher, error) {
|
||||
n, e := g.subscribe()
|
||||
return newGossipWatcher(n, e, opts...)
|
||||
}
|
||||
|
||||
func (g *gossipRegistry) String() string {
|
||||
return "gossip"
|
||||
}
|
||||
|
||||
func NewRegistry(opts ...registry.Option) registry.Registry {
|
||||
gossip := &gossipRegistry{
|
||||
options: registry.Options{},
|
||||
updates: make(chan *update, 100),
|
||||
services: make(map[string][]*registry.Service),
|
||||
watchers: make(map[string]chan *registry.Result),
|
||||
}
|
||||
|
||||
// configure the gossiper
|
||||
if err := configure(gossip, opts...); err != nil {
|
||||
log.Fatal("Error configuring registry: %v", err)
|
||||
}
|
||||
|
||||
// run the updater
|
||||
go gossip.run()
|
||||
|
||||
// wait for setup
|
||||
<-time.After(gossip.interval * 2)
|
||||
|
||||
return gossip
|
||||
}
|
17
registry/gossip/options.go
Normal file
17
registry/gossip/options.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package gossip
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
type contextSecretKey struct{}
|
||||
|
||||
// Secret specifies an encryption key. The value should be either
|
||||
// 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
|
||||
func Secret(k []byte) registry.Option {
|
||||
return func(o *registry.Options) {
|
||||
o.Context = context.WithValue(o.Context, contextSecretKey{}, k)
|
||||
}
|
||||
}
|
28
registry/gossip/proto/gossip.micro.go
Normal file
28
registry/gossip/proto/gossip.micro.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||
// source: github.com/micro/go-micro/registry/gossip/proto/gossip.proto
|
||||
|
||||
/*
|
||||
Package gossip is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
github.com/micro/go-micro/registry/gossip/proto/gossip.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Update
|
||||
*/
|
||||
package gossip
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
145
registry/gossip/proto/gossip.pb.go
Normal file
145
registry/gossip/proto/gossip.pb.go
Normal file
@@ -0,0 +1,145 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: github.com/micro/go-micro/registry/gossip/proto/gossip.proto
|
||||
|
||||
package gossip
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// Update is the message broadcast
|
||||
type Update struct {
|
||||
// unique id of update
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
// unix nano timestamp of update
|
||||
Timestamp uint64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||
// time to live for entry
|
||||
Expires uint64 `protobuf:"varint,3,opt,name=expires,proto3" json:"expires,omitempty"`
|
||||
// type of update; service
|
||||
Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
|
||||
// what action is taken; add, del, put
|
||||
Action string `protobuf:"bytes,5,opt,name=action,proto3" json:"action,omitempty"`
|
||||
// any other associated metadata about the data
|
||||
Metadata map[string]string `protobuf:"bytes,6,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
// the payload data;
|
||||
Data []byte `protobuf:"bytes,7,opt,name=data,proto3" json:"data,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Update) Reset() { *m = Update{} }
|
||||
func (m *Update) String() string { return proto.CompactTextString(m) }
|
||||
func (*Update) ProtoMessage() {}
|
||||
func (*Update) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_18cba623e76e57f3, []int{0}
|
||||
}
|
||||
|
||||
func (m *Update) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Update.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Update) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Update.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Update) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Update.Merge(m, src)
|
||||
}
|
||||
func (m *Update) XXX_Size() int {
|
||||
return xxx_messageInfo_Update.Size(m)
|
||||
}
|
||||
func (m *Update) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Update.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Update proto.InternalMessageInfo
|
||||
|
||||
func (m *Update) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Update) GetTimestamp() uint64 {
|
||||
if m != nil {
|
||||
return m.Timestamp
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Update) GetExpires() uint64 {
|
||||
if m != nil {
|
||||
return m.Expires
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Update) GetType() string {
|
||||
if m != nil {
|
||||
return m.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Update) GetAction() string {
|
||||
if m != nil {
|
||||
return m.Action
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Update) GetMetadata() map[string]string {
|
||||
if m != nil {
|
||||
return m.Metadata
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Update) GetData() []byte {
|
||||
if m != nil {
|
||||
return m.Data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Update)(nil), "gossip.Update")
|
||||
proto.RegisterMapType((map[string]string)(nil), "gossip.Update.MetadataEntry")
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterFile("github.com/micro/go-micro/registry/gossip/proto/gossip.proto", fileDescriptor_18cba623e76e57f3)
|
||||
}
|
||||
|
||||
var fileDescriptor_18cba623e76e57f3 = []byte{
|
||||
// 251 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x90, 0xcf, 0x4a, 0xc4, 0x30,
|
||||
0x10, 0x87, 0x69, 0xb6, 0x9b, 0xb5, 0xe3, 0x1f, 0x64, 0x10, 0x09, 0xb2, 0x87, 0xe2, 0xa9, 0x17,
|
||||
0x5b, 0xd0, 0xcb, 0xa2, 0x5e, 0x3d, 0x7a, 0x09, 0xf8, 0x00, 0xd9, 0x36, 0xd4, 0xa0, 0xd9, 0x84,
|
||||
0x64, 0x56, 0xec, 0x13, 0xf8, 0xda, 0xb2, 0x69, 0x54, 0xbc, 0x7d, 0xdf, 0xcc, 0x24, 0x99, 0x5f,
|
||||
0xe0, 0x71, 0x34, 0xf4, 0xba, 0xdf, 0xb6, 0xbd, 0xb3, 0x9d, 0x35, 0x7d, 0x70, 0xdd, 0xe8, 0x6e,
|
||||
0x66, 0x08, 0x7a, 0x34, 0x91, 0xc2, 0xd4, 0x8d, 0x2e, 0x46, 0xe3, 0x3b, 0x1f, 0x1c, 0xb9, 0x2c,
|
||||
0x6d, 0x12, 0xe4, 0xb3, 0x5d, 0x7f, 0x31, 0xe0, 0x2f, 0x7e, 0x50, 0xa4, 0xf1, 0x0c, 0x98, 0x19,
|
||||
0x44, 0x51, 0x17, 0x4d, 0x25, 0x99, 0x19, 0x70, 0x0d, 0x15, 0x19, 0xab, 0x23, 0x29, 0xeb, 0x05,
|
||||
0xab, 0x8b, 0xa6, 0x94, 0x7f, 0x05, 0x14, 0xb0, 0xd2, 0x9f, 0xde, 0x04, 0x1d, 0xc5, 0x22, 0xf5,
|
||||
0x7e, 0x14, 0x11, 0x4a, 0x9a, 0xbc, 0x16, 0x65, 0xba, 0x29, 0x31, 0x5e, 0x02, 0x57, 0x3d, 0x19,
|
||||
0xb7, 0x13, 0xcb, 0x54, 0xcd, 0x86, 0x1b, 0x38, 0xb2, 0x9a, 0xd4, 0xa0, 0x48, 0x09, 0x5e, 0x2f,
|
||||
0x9a, 0xe3, 0xdb, 0x75, 0x9b, 0xf7, 0x9c, 0xb7, 0x6a, 0x9f, 0x73, 0xfb, 0x69, 0x47, 0x61, 0x92,
|
||||
0xbf, 0xd3, 0x87, 0x57, 0xd2, 0xa9, 0x55, 0x5d, 0x34, 0x27, 0x32, 0xf1, 0xd5, 0x03, 0x9c, 0xfe,
|
||||
0x1b, 0xc7, 0x73, 0x58, 0xbc, 0xe9, 0x29, 0x67, 0x3a, 0x20, 0x5e, 0xc0, 0xf2, 0x43, 0xbd, 0xef,
|
||||
0x75, 0x0a, 0x54, 0xc9, 0x59, 0xee, 0xd9, 0xa6, 0xd8, 0xf2, 0xf4, 0x31, 0x77, 0xdf, 0x01, 0x00,
|
||||
0x00, 0xff, 0xff, 0x06, 0x6e, 0x00, 0x3c, 0x58, 0x01, 0x00, 0x00,
|
||||
}
|
21
registry/gossip/proto/gossip.proto
Normal file
21
registry/gossip/proto/gossip.proto
Normal file
@@ -0,0 +1,21 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package gossip;
|
||||
|
||||
// Update is the message broadcast
|
||||
message Update {
|
||||
// unique id of update
|
||||
string id = 1;
|
||||
// unix nano timestamp of update
|
||||
uint64 timestamp = 2;
|
||||
// time to live for entry
|
||||
uint64 expires = 3;
|
||||
// type of update; service
|
||||
string type = 4;
|
||||
// what action is taken; add, del, put
|
||||
string action = 5;
|
||||
// any other associated metadata about the data
|
||||
map<string, string> metadata = 6;
|
||||
// the payload data;
|
||||
bytes data = 7;
|
||||
}
|
109
registry/gossip/util.go
Normal file
109
registry/gossip/util.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package gossip
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
func cp(current []*registry.Service) []*registry.Service {
|
||||
var services []*registry.Service
|
||||
|
||||
for _, service := range current {
|
||||
// copy service
|
||||
s := new(registry.Service)
|
||||
*s = *service
|
||||
|
||||
// copy nodes
|
||||
var nodes []*registry.Node
|
||||
for _, node := range service.Nodes {
|
||||
n := new(registry.Node)
|
||||
*n = *node
|
||||
nodes = append(nodes, n)
|
||||
}
|
||||
s.Nodes = nodes
|
||||
|
||||
// copy endpoints
|
||||
var eps []*registry.Endpoint
|
||||
for _, ep := range service.Endpoints {
|
||||
e := new(registry.Endpoint)
|
||||
*e = *ep
|
||||
eps = append(eps, e)
|
||||
}
|
||||
s.Endpoints = eps
|
||||
|
||||
// append service
|
||||
services = append(services, s)
|
||||
}
|
||||
|
||||
return services
|
||||
}
|
||||
|
||||
func addNodes(old, neu []*registry.Node) []*registry.Node {
|
||||
for _, n := range neu {
|
||||
var seen bool
|
||||
for i, o := range old {
|
||||
if o.Id == n.Id {
|
||||
seen = true
|
||||
old[i] = n
|
||||
break
|
||||
}
|
||||
}
|
||||
if !seen {
|
||||
old = append(old, n)
|
||||
}
|
||||
}
|
||||
return old
|
||||
}
|
||||
|
||||
func addServices(old, neu []*registry.Service) []*registry.Service {
|
||||
for _, s := range neu {
|
||||
var seen bool
|
||||
for i, o := range old {
|
||||
if o.Version == s.Version {
|
||||
s.Nodes = addNodes(o.Nodes, s.Nodes)
|
||||
seen = true
|
||||
old[i] = s
|
||||
break
|
||||
}
|
||||
}
|
||||
if !seen {
|
||||
old = append(old, s)
|
||||
}
|
||||
}
|
||||
return old
|
||||
}
|
||||
|
||||
func delNodes(old, del []*registry.Node) []*registry.Node {
|
||||
var nodes []*registry.Node
|
||||
for _, o := range old {
|
||||
var rem bool
|
||||
for _, n := range del {
|
||||
if o.Id == n.Id {
|
||||
rem = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !rem {
|
||||
nodes = append(nodes, o)
|
||||
}
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
func delServices(old, del []*registry.Service) []*registry.Service {
|
||||
var services []*registry.Service
|
||||
for i, o := range old {
|
||||
var rem bool
|
||||
for _, s := range del {
|
||||
if o.Version == s.Version {
|
||||
old[i].Nodes = delNodes(o.Nodes, s.Nodes)
|
||||
if len(old[i].Nodes) == 0 {
|
||||
rem = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !rem {
|
||||
services = append(services, o)
|
||||
}
|
||||
}
|
||||
return services
|
||||
}
|
78
registry/gossip/util_test.go
Normal file
78
registry/gossip/util_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package gossip
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
func TestDelServices(t *testing.T) {
|
||||
services := []*registry.Service{
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-123",
|
||||
Address: "localhost",
|
||||
Port: 9999,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-123",
|
||||
Address: "localhost",
|
||||
Port: 6666,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
servs := delServices([]*registry.Service{services[0]}, []*registry.Service{services[1]})
|
||||
if i := len(servs); i > 0 {
|
||||
t.Errorf("Expected 0 nodes, got %d: %+v", i, servs)
|
||||
}
|
||||
t.Logf("Services %+v", servs)
|
||||
}
|
||||
|
||||
func TestDelNodes(t *testing.T) {
|
||||
services := []*registry.Service{
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-123",
|
||||
Address: "localhost",
|
||||
Port: 9999,
|
||||
},
|
||||
{
|
||||
Id: "foo-321",
|
||||
Address: "localhost",
|
||||
Port: 6666,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "foo-123",
|
||||
Address: "localhost",
|
||||
Port: 6666,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
nodes := delNodes(services[0].Nodes, services[1].Nodes)
|
||||
if i := len(nodes); i != 1 {
|
||||
t.Errorf("Expected only 1 node, got %d: %+v", i, nodes)
|
||||
}
|
||||
t.Logf("Nodes %+v", nodes)
|
||||
}
|
51
registry/gossip/watcher.go
Normal file
51
registry/gossip/watcher.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package gossip
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
type gossipWatcher struct {
|
||||
wo registry.WatchOptions
|
||||
next chan *registry.Result
|
||||
stop chan bool
|
||||
}
|
||||
|
||||
func newGossipWatcher(ch chan *registry.Result, stop chan bool, opts ...registry.WatchOption) (registry.Watcher, error) {
|
||||
var wo registry.WatchOptions
|
||||
for _, o := range opts {
|
||||
o(&wo)
|
||||
}
|
||||
|
||||
return &gossipWatcher{
|
||||
wo: wo,
|
||||
next: ch,
|
||||
stop: stop,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *gossipWatcher) Next() (*registry.Result, error) {
|
||||
for {
|
||||
select {
|
||||
case r, ok := <-m.next:
|
||||
if !ok {
|
||||
return nil, registry.ErrWatcherStopped
|
||||
}
|
||||
// check watch options
|
||||
if len(m.wo.Service) > 0 && r.Service.Name != m.wo.Service {
|
||||
continue
|
||||
}
|
||||
return r, nil
|
||||
case <-m.stop:
|
||||
return nil, registry.ErrWatcherStopped
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *gossipWatcher) Stop() {
|
||||
select {
|
||||
case <-m.stop:
|
||||
return
|
||||
default:
|
||||
close(m.stop)
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
// Package mdns is a multicast dns registry
|
||||
package mdns
|
||||
|
||||
/*
|
||||
@@ -49,6 +50,17 @@ func newRegistry(opts ...registry.Option) registry.Registry {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) Init(opts ...registry.Option) error {
|
||||
for _, o := range opts {
|
||||
o(&m.opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) Options() registry.Options {
|
||||
return m.opts
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) Register(service *registry.Service, opts ...registry.RegisterOption) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
@@ -297,8 +309,14 @@ func (m *mdnsRegistry) ListServices() ([]*registry.Service, error) {
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) Watch() (registry.Watcher, error) {
|
||||
func (m *mdnsRegistry) Watch(opts ...registry.WatchOption) (registry.Watcher, error) {
|
||||
var wo registry.WatchOptions
|
||||
for _, o := range opts {
|
||||
o(&wo)
|
||||
}
|
||||
|
||||
md := &mdnsWatcher{
|
||||
wo: wo,
|
||||
ch: make(chan *mdns.ServiceEntry, 32),
|
||||
exit: make(chan struct{}),
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
)
|
||||
|
||||
type mdnsWatcher struct {
|
||||
wo registry.WatchOptions
|
||||
ch chan *mdns.ServiceEntry
|
||||
exit chan struct{}
|
||||
}
|
||||
@@ -26,6 +27,12 @@ func (m *mdnsWatcher) Next() (*registry.Result, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter watch options
|
||||
// wo.Service: Only keep services we care about
|
||||
if len(m.wo.Service) > 0 && txt.Service != m.wo.Service {
|
||||
continue
|
||||
}
|
||||
|
||||
var action string
|
||||
|
||||
if e.TTL == 0 {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// Package mock provides a mock registry for testing
|
||||
package mock
|
||||
|
||||
import (
|
||||
@@ -87,15 +88,27 @@ func (m *mockRegistry) Deregister(s *registry.Service) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockRegistry) Watch() (registry.Watcher, error) {
|
||||
return &mockWatcher{exit: make(chan bool)}, nil
|
||||
func (m *mockRegistry) Watch(opts ...registry.WatchOption) (registry.Watcher, error) {
|
||||
var wopts registry.WatchOptions
|
||||
for _, o := range opts {
|
||||
o(&wopts)
|
||||
}
|
||||
return &mockWatcher{exit: make(chan bool), opts: wopts}, nil
|
||||
}
|
||||
|
||||
func (m *mockRegistry) String() string {
|
||||
return "mock"
|
||||
}
|
||||
|
||||
func NewRegistry() registry.Registry {
|
||||
func (m *mockRegistry) Init(opts ...registry.Option) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockRegistry) Options() registry.Options {
|
||||
return registry.Options{}
|
||||
}
|
||||
|
||||
func NewRegistry(opts ...registry.Options) registry.Registry {
|
||||
m := &mockRegistry{Services: make(map[string][]*registry.Service)}
|
||||
m.init()
|
||||
return m
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
type mockWatcher struct {
|
||||
exit chan bool
|
||||
opts registry.WatchOptions
|
||||
}
|
||||
|
||||
func (m *mockWatcher) Next() (*registry.Result, error) {
|
||||
|
@@ -1,10 +1,9 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
@@ -25,6 +24,15 @@ type RegisterOptions struct {
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
type WatchOptions struct {
|
||||
// Specify a service to watch
|
||||
// If blank, the watch is for all services
|
||||
Service string
|
||||
// Other options for implementations of the interface
|
||||
// can be stored in a context
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// Addrs is the registry addresses to use
|
||||
func Addrs(addrs ...string) Option {
|
||||
return func(o *Options) {
|
||||
@@ -57,3 +65,10 @@ func RegisterTTL(t time.Duration) RegisterOption {
|
||||
o.TTL = t
|
||||
}
|
||||
}
|
||||
|
||||
// Watch a service
|
||||
func WatchService(name string) WatchOption {
|
||||
return func(o *WatchOptions) {
|
||||
o.Service = name
|
||||
}
|
||||
}
|
||||
|
@@ -9,11 +9,13 @@ import (
|
||||
// and an abstraction over varying implementations
|
||||
// {consul, etcd, zookeeper, ...}
|
||||
type Registry interface {
|
||||
Init(...Option) error
|
||||
Options() Options
|
||||
Register(*Service, ...RegisterOption) error
|
||||
Deregister(*Service) error
|
||||
GetService(string) ([]*Service, error)
|
||||
ListServices() ([]*Service, error)
|
||||
Watch() (Watcher, error)
|
||||
Watch(...WatchOption) (Watcher, error)
|
||||
String() string
|
||||
}
|
||||
|
||||
@@ -21,10 +23,15 @@ type Option func(*Options)
|
||||
|
||||
type RegisterOption func(*RegisterOptions)
|
||||
|
||||
type WatchOption func(*WatchOptions)
|
||||
|
||||
var (
|
||||
DefaultRegistry = newConsulRegistry()
|
||||
|
||||
// Not found error when GetService is called
|
||||
ErrNotFound = errors.New("not found")
|
||||
// Watcher stopped error when watcher is stopped
|
||||
ErrWatcherStopped = errors.New("watcher stopped")
|
||||
)
|
||||
|
||||
func NewRegistry(opts ...Option) Registry {
|
||||
@@ -52,8 +59,8 @@ func ListServices() ([]*Service, error) {
|
||||
}
|
||||
|
||||
// Watch returns a watcher which allows you to track updates to the registry.
|
||||
func Watch() (Watcher, error) {
|
||||
return DefaultRegistry.Watch()
|
||||
func Watch(opts ...WatchOption) (Watcher, error) {
|
||||
return DefaultRegistry.Watch(opts...)
|
||||
}
|
||||
|
||||
func String() string {
|
||||
|
110
selector/cache/cache.go
vendored
110
selector/cache/cache.go
vendored
@@ -1,3 +1,4 @@
|
||||
// Package cache is a caching selector. It uses the registry watcher.
|
||||
package cache
|
||||
|
||||
import (
|
||||
@@ -9,21 +10,16 @@ import (
|
||||
"github.com/micro/go-micro/selector"
|
||||
)
|
||||
|
||||
/*
|
||||
Cache selector is a selector which uses the registry.Watcher to Cache service entries.
|
||||
It defaults to a TTL for 1 minute and causes a cache miss on the next request.
|
||||
*/
|
||||
|
||||
type cacheSelector struct {
|
||||
so selector.Options
|
||||
ttl time.Duration
|
||||
|
||||
// registry cache
|
||||
sync.Mutex
|
||||
sync.RWMutex
|
||||
cache map[string][]*registry.Service
|
||||
ttls map[string]time.Time
|
||||
|
||||
once sync.Once
|
||||
watched map[string]bool
|
||||
|
||||
// used to close or reload watcher
|
||||
reload chan bool
|
||||
@@ -85,11 +81,25 @@ func (c *cacheSelector) del(service string) {
|
||||
}
|
||||
|
||||
func (c *cacheSelector) get(service string) ([]*registry.Service, error) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
// read lock
|
||||
c.RLock()
|
||||
|
||||
// get does the actual request for a service
|
||||
// it also caches it
|
||||
// check the cache first
|
||||
services, ok := c.cache[service]
|
||||
// get cache ttl
|
||||
ttl, kk := c.ttls[service]
|
||||
|
||||
// got services && within ttl so return cache
|
||||
if ok && kk && time.Since(ttl) < c.ttl {
|
||||
// make a copy
|
||||
cp := c.cp(services)
|
||||
// unlock the read
|
||||
c.RUnlock()
|
||||
// return servics
|
||||
return cp, nil
|
||||
}
|
||||
|
||||
// get does the actual request for a service and cache it
|
||||
get := func(service string) ([]*registry.Service, error) {
|
||||
// ask the registry
|
||||
services, err := c.so.Registry.GetService(service)
|
||||
@@ -98,43 +108,23 @@ func (c *cacheSelector) get(service string) ([]*registry.Service, error) {
|
||||
}
|
||||
|
||||
// cache results
|
||||
c.Lock()
|
||||
c.set(service, c.cp(services))
|
||||
c.Unlock()
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// check the cache first
|
||||
services, ok := c.cache[service]
|
||||
|
||||
// cache miss or no services
|
||||
if !ok || len(services) == 0 {
|
||||
return get(service)
|
||||
// watch service if not watched
|
||||
if _, ok := c.watched[service]; !ok {
|
||||
go c.run(service)
|
||||
}
|
||||
|
||||
// got cache but lets check ttl
|
||||
ttl, kk := c.ttls[service]
|
||||
// unlock the read lock
|
||||
c.RUnlock()
|
||||
|
||||
// within ttl so return cache
|
||||
if kk && time.Since(ttl) < c.ttl {
|
||||
return c.cp(services), nil
|
||||
}
|
||||
|
||||
// expired entry so get service
|
||||
services, err := get(service)
|
||||
|
||||
// no error then return error
|
||||
if err == nil {
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// not found error then return
|
||||
if err == registry.ErrNotFound {
|
||||
return nil, selector.ErrNotFound
|
||||
}
|
||||
|
||||
// other error
|
||||
|
||||
// return expired cache as last resort
|
||||
return c.cp(services), nil
|
||||
// get and return services
|
||||
return get(service)
|
||||
}
|
||||
|
||||
func (c *cacheSelector) set(service string, services []*registry.Service) {
|
||||
@@ -254,7 +244,19 @@ func (c *cacheSelector) update(res *registry.Result) {
|
||||
// it creates a new watcher if there's a problem
|
||||
// reloads the watcher if Init is called
|
||||
// and returns when Close is called
|
||||
func (c *cacheSelector) run() {
|
||||
func (c *cacheSelector) run(name string) {
|
||||
// set watcher
|
||||
c.Lock()
|
||||
c.watched[name] = true
|
||||
c.Unlock()
|
||||
|
||||
// delete watcher on exit
|
||||
defer func() {
|
||||
c.Lock()
|
||||
delete(c.watched, name)
|
||||
c.Unlock()
|
||||
}()
|
||||
|
||||
for {
|
||||
// exit early if already dead
|
||||
if c.quit() {
|
||||
@@ -262,7 +264,9 @@ func (c *cacheSelector) run() {
|
||||
}
|
||||
|
||||
// create new watcher
|
||||
w, err := c.so.Registry.Watch()
|
||||
w, err := c.so.Registry.Watch(
|
||||
registry.WatchService(name),
|
||||
)
|
||||
if err != nil {
|
||||
if c.quit() {
|
||||
return
|
||||
@@ -332,10 +336,6 @@ func (c *cacheSelector) Options() selector.Options {
|
||||
}
|
||||
|
||||
func (c *cacheSelector) Select(service string, opts ...selector.SelectOption) (selector.Next, error) {
|
||||
c.once.Do(func() {
|
||||
go c.run()
|
||||
})
|
||||
|
||||
sopts := selector.SelectOptions{
|
||||
Strategy: c.so.Strategy,
|
||||
}
|
||||
@@ -366,17 +366,16 @@ func (c *cacheSelector) Select(service string, opts ...selector.SelectOption) (s
|
||||
}
|
||||
|
||||
func (c *cacheSelector) Mark(service string, node *registry.Node, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cacheSelector) Reset(service string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Close stops the watcher and destroys the cache
|
||||
func (c *cacheSelector) Close() error {
|
||||
c.Lock()
|
||||
c.cache = make(map[string][]*registry.Service)
|
||||
c.watched = make(map[string]bool)
|
||||
c.Unlock()
|
||||
|
||||
select {
|
||||
@@ -414,11 +413,12 @@ func NewSelector(opts ...selector.Option) selector.Selector {
|
||||
}
|
||||
|
||||
return &cacheSelector{
|
||||
so: sopts,
|
||||
ttl: ttl,
|
||||
cache: make(map[string][]*registry.Service),
|
||||
ttls: make(map[string]time.Time),
|
||||
reload: make(chan bool, 1),
|
||||
exit: make(chan bool),
|
||||
so: sopts,
|
||||
ttl: ttl,
|
||||
watched: make(map[string]bool),
|
||||
cache: make(map[string][]*registry.Service),
|
||||
ttls: make(map[string]time.Time),
|
||||
reload: make(chan bool, 1),
|
||||
exit: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
2
selector/cache/options.go
vendored
2
selector/cache/options.go
vendored
@@ -1,10 +1,10 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/selector"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type ttlKey struct{}
|
||||
|
@@ -48,11 +48,9 @@ func (r *defaultSelector) Select(service string, opts ...SelectOption) (Next, er
|
||||
}
|
||||
|
||||
func (r *defaultSelector) Mark(service string, node *registry.Node, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (r *defaultSelector) Reset(service string) {
|
||||
return
|
||||
}
|
||||
|
||||
func (r *defaultSelector) Close() error {
|
||||
|
@@ -80,7 +80,7 @@ func TestFilterEndpoint(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if seen == false && data.count > 0 {
|
||||
if !seen && data.count > 0 {
|
||||
t.Fatalf("Expected %d services but seen is %t; result %+v", data.count, seen, services)
|
||||
}
|
||||
}
|
||||
@@ -232,7 +232,7 @@ func TestFilterVersion(t *testing.T) {
|
||||
seen = true
|
||||
}
|
||||
|
||||
if seen == false && data.count > 0 {
|
||||
if !seen && data.count > 0 {
|
||||
t.Fatalf("Expected %d services but seen is %t; result %+v", data.count, seen, services)
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
package selector
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
"context"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"context"
|
||||
)
|
||||
|
||||
type serverKey struct{}
|
||||
|
@@ -1,12 +1,11 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
proto "github.com/micro/go-micro/server/debug/proto"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// The debug handler represents an internal server handler
|
||||
|
@@ -1,11 +1,11 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/registry"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type testHandler struct{}
|
||||
|
@@ -1,37 +1,5 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
// Handler interface represents a Service request handler. It's generated
|
||||
// by passing any type of public concrete object with methods into server.NewHandler.
|
||||
// Most will pass in a struct.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Service struct {}
|
||||
//
|
||||
// func (s *Service) Method(context, request, response) error {
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
type Handler interface {
|
||||
Name() string
|
||||
Handler() interface{}
|
||||
Endpoints() []*registry.Endpoint
|
||||
Options() HandlerOptions
|
||||
}
|
||||
|
||||
// Subscriber interface represents a subscription to a given topic using
|
||||
// a specific subscriber function or object with methods.
|
||||
type Subscriber interface {
|
||||
Topic() string
|
||||
Subscriber() interface{}
|
||||
Endpoints() []*registry.Endpoint
|
||||
Options() SubscriberOptions
|
||||
}
|
||||
|
||||
type HandlerOptions struct {
|
||||
Internal bool
|
||||
Metadata map[string]map[string]string
|
||||
|
@@ -4,8 +4,8 @@ import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/server"
|
||||
"github.com/pborman/uuid"
|
||||
)
|
||||
|
||||
type MockServer struct {
|
||||
@@ -69,7 +69,7 @@ func (m *MockServer) NewHandler(h interface{}, opts ...server.HandlerOption) ser
|
||||
}
|
||||
|
||||
return &MockHandler{
|
||||
Id: uuid.NewUUID().String(),
|
||||
Id: uuid.New().String(),
|
||||
Hdlr: h,
|
||||
Opts: options,
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/broker"
|
||||
@@ -8,8 +9,6 @@ import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
"github.com/micro/go-micro/server/debug"
|
||||
"github.com/micro/go-micro/transport"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
|
@@ -1,9 +1,11 @@
|
||||
// Package rpc provides an rpc server
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/server"
|
||||
)
|
||||
|
||||
// NewServer returns a micro server interface
|
||||
func NewServer(opts ...server.Option) server.Server {
|
||||
return server.NewServer(opts...)
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type rpcPlusCodec struct {
|
||||
type rpcCodec struct {
|
||||
socket transport.Socket
|
||||
codec codec.Codec
|
||||
|
||||
@@ -47,12 +47,12 @@ func (rwc *readWriteCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newRpcPlusCodec(req *transport.Message, socket transport.Socket, c codec.NewCodec) serverCodec {
|
||||
func newRpcCodec(req *transport.Message, socket transport.Socket, c codec.NewCodec) serverCodec {
|
||||
rwc := &readWriteCloser{
|
||||
rbuf: bytes.NewBuffer(req.Body),
|
||||
wbuf: bytes.NewBuffer(nil),
|
||||
}
|
||||
r := &rpcPlusCodec{
|
||||
r := &rpcCodec{
|
||||
buf: rwc,
|
||||
codec: c(rwc),
|
||||
req: req,
|
||||
@@ -61,7 +61,7 @@ func newRpcPlusCodec(req *transport.Message, socket transport.Socket, c codec.Ne
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *rpcPlusCodec) ReadRequestHeader(r *request, first bool) error {
|
||||
func (c *rpcCodec) ReadRequestHeader(r *request, first bool) error {
|
||||
m := codec.Message{Header: c.req.Header}
|
||||
|
||||
if !first {
|
||||
@@ -83,11 +83,11 @@ func (c *rpcPlusCodec) ReadRequestHeader(r *request, first bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *rpcPlusCodec) ReadRequestBody(b interface{}) error {
|
||||
func (c *rpcCodec) ReadRequestBody(b interface{}) error {
|
||||
return c.codec.ReadBody(b)
|
||||
}
|
||||
|
||||
func (c *rpcPlusCodec) WriteResponse(r *response, body interface{}, last bool) error {
|
||||
func (c *rpcCodec) WriteResponse(r *response, body interface{}, last bool) error {
|
||||
c.buf.wbuf.Reset()
|
||||
m := &codec.Message{
|
||||
Method: r.ServiceMethod,
|
||||
@@ -111,7 +111,7 @@ func (c *rpcPlusCodec) WriteResponse(r *response, body interface{}, last bool) e
|
||||
})
|
||||
}
|
||||
|
||||
func (c *rpcPlusCodec) Close() error {
|
||||
func (c *rpcCodec) Close() error {
|
||||
c.buf.Close()
|
||||
c.codec.Close()
|
||||
return c.socket.Close()
|
||||
|
@@ -15,6 +15,8 @@ type testCodec struct {
|
||||
}
|
||||
|
||||
type testSocket struct {
|
||||
local string
|
||||
remote string
|
||||
}
|
||||
|
||||
// TestCodecWriteError simulates what happens when a codec is unable
|
||||
@@ -36,7 +38,7 @@ func TestCodecWriteError(t *testing.T) {
|
||||
wbuf: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
c := rpcPlusCodec{
|
||||
c := rpcCodec{
|
||||
buf: &rwc,
|
||||
codec: &testCodec{
|
||||
buf: rwc.wbuf,
|
||||
@@ -87,6 +89,14 @@ func (c *testCodec) String() string {
|
||||
return "string"
|
||||
}
|
||||
|
||||
func (s testSocket) Local() string {
|
||||
return s.local
|
||||
}
|
||||
|
||||
func (s testSocket) Remote() string {
|
||||
return s.remote
|
||||
}
|
||||
|
||||
func (s testSocket) Recv(message *transport.Message) error {
|
||||
return nil
|
||||
}
|
||||
|
@@ -8,10 +8,10 @@ type rpcRequest struct {
|
||||
stream bool
|
||||
}
|
||||
|
||||
type rpcPublication struct {
|
||||
type rpcMessage struct {
|
||||
topic string
|
||||
contentType string
|
||||
message interface{}
|
||||
payload interface{}
|
||||
}
|
||||
|
||||
func (r *rpcRequest) ContentType() string {
|
||||
@@ -34,14 +34,14 @@ func (r *rpcRequest) Stream() bool {
|
||||
return r.stream
|
||||
}
|
||||
|
||||
func (r *rpcPublication) ContentType() string {
|
||||
func (r *rpcMessage) ContentType() string {
|
||||
return r.contentType
|
||||
}
|
||||
|
||||
func (r *rpcPublication) Topic() string {
|
||||
func (r *rpcMessage) Topic() string {
|
||||
return r.topic
|
||||
}
|
||||
|
||||
func (r *rpcPublication) Message() interface{} {
|
||||
return r.message
|
||||
func (r *rpcMessage) Payload() interface{} {
|
||||
return r.payload
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
@@ -16,9 +17,7 @@ import (
|
||||
"github.com/micro/go-micro/registry"
|
||||
"github.com/micro/go-micro/transport"
|
||||
|
||||
"github.com/micro/misc/lib/addr"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"github.com/micro/util/go/lib/addr"
|
||||
)
|
||||
|
||||
type rpcServer struct {
|
||||
@@ -67,6 +66,9 @@ func (s *rpcServer) accept(sock transport.Socket) {
|
||||
return
|
||||
}
|
||||
|
||||
// add to wait group
|
||||
s.wg.Add(1)
|
||||
|
||||
// we use this Timeout header to set a server deadline
|
||||
to := msg.Header["Timeout"]
|
||||
// we use this Content-Type header to identify the codec needed
|
||||
@@ -81,10 +83,11 @@ func (s *rpcServer) accept(sock transport.Socket) {
|
||||
},
|
||||
Body: []byte(err.Error()),
|
||||
})
|
||||
s.wg.Done()
|
||||
return
|
||||
}
|
||||
|
||||
codec := newRpcPlusCodec(&msg, sock, cf)
|
||||
codec := newRpcCodec(&msg, sock, cf)
|
||||
|
||||
// strip our headers
|
||||
hdr := make(map[string]string)
|
||||
@@ -94,6 +97,10 @@ func (s *rpcServer) accept(sock transport.Socket) {
|
||||
delete(hdr, "Content-Type")
|
||||
delete(hdr, "Timeout")
|
||||
|
||||
// set local/remote ips
|
||||
hdr["Local"] = sock.Local()
|
||||
hdr["Remote"] = sock.Remote()
|
||||
|
||||
ctx := metadata.NewContext(context.Background(), hdr)
|
||||
|
||||
// set the timeout if we have it
|
||||
@@ -103,15 +110,13 @@ func (s *rpcServer) accept(sock transport.Socket) {
|
||||
}
|
||||
}
|
||||
|
||||
// add to wait group
|
||||
s.wg.Add(1)
|
||||
defer s.wg.Done()
|
||||
|
||||
// TODO: needs better error handling
|
||||
if err := s.rpc.serveRequest(ctx, codec, ct); err != nil {
|
||||
s.wg.Done()
|
||||
log.Logf("Unexpected error serving request, closing socket: %v", err)
|
||||
return
|
||||
}
|
||||
s.wg.Done()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,14 +392,40 @@ func (s *rpcServer) Start() error {
|
||||
|
||||
log.Logf("Listening on %s", ts.Addr())
|
||||
s.Lock()
|
||||
// swap address
|
||||
addr := s.opts.Address
|
||||
s.opts.Address = ts.Addr()
|
||||
s.Unlock()
|
||||
|
||||
go ts.Accept(s.accept)
|
||||
exit := make(chan bool, 1)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
err := ts.Accept(s.accept)
|
||||
|
||||
// check if we're supposed to exit
|
||||
select {
|
||||
case <-exit:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// check the error and backoff
|
||||
if err != nil {
|
||||
log.Logf("Accept error: %v", err)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
// no error just exit
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// wait for exit
|
||||
ch := <-s.exit
|
||||
exit <- true
|
||||
|
||||
// wait for requests to finish
|
||||
if wait(s.opts.Context) {
|
||||
@@ -406,6 +437,11 @@ func (s *rpcServer) Start() error {
|
||||
|
||||
// disconnect the broker
|
||||
config.Broker.Disconnect()
|
||||
|
||||
s.Lock()
|
||||
// swap back address
|
||||
s.opts.Address = addr
|
||||
s.Unlock()
|
||||
}()
|
||||
|
||||
// TODO: subscribe to cruft
|
||||
|
@@ -7,6 +7,7 @@ package server
|
||||
// Meh, we need to get rid of this shit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"reflect"
|
||||
@@ -16,7 +17,6 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/micro/go-log"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -119,9 +119,9 @@ func prepareMethod(method reflect.Method) *methodType {
|
||||
|
||||
if stream {
|
||||
// check stream type
|
||||
streamType := reflect.TypeOf((*Streamer)(nil)).Elem()
|
||||
streamType := reflect.TypeOf((*Stream)(nil)).Elem()
|
||||
if !argType.Implements(streamType) {
|
||||
log.Log(mname, "argument does not implement Streamer interface:", argType)
|
||||
log.Log(mname, "argument does not implement Stream interface:", argType)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
@@ -199,7 +199,7 @@ func (server *server) register(rcvr interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (server *server) sendResponse(sending *sync.Mutex, req *request, reply interface{}, codec serverCodec, errmsg string, last bool) (err error) {
|
||||
func (server *server) sendResponse(sending sync.Locker, req *request, reply interface{}, codec serverCodec, errmsg string, last bool) (err error) {
|
||||
resp := server.getResponse()
|
||||
// Encode the response header
|
||||
resp.ServiceMethod = req.ServiceMethod
|
||||
|
@@ -1,9 +1,8 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Implements the Streamer interface
|
||||
|
@@ -2,15 +2,17 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-log"
|
||||
"github.com/pborman/uuid"
|
||||
"golang.org/x/net/context"
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
// Server is a simple micro server abstraction
|
||||
type Server interface {
|
||||
Options() Options
|
||||
Init(...Option) error
|
||||
@@ -25,12 +27,14 @@ type Server interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
type Publication interface {
|
||||
// Message is an async message interface
|
||||
type Message interface {
|
||||
Topic() string
|
||||
Message() interface{}
|
||||
Payload() interface{}
|
||||
ContentType() string
|
||||
}
|
||||
|
||||
// Request is a synchronous request interface
|
||||
type Request interface {
|
||||
Service() string
|
||||
Method() string
|
||||
@@ -40,11 +44,11 @@ type Request interface {
|
||||
Stream() bool
|
||||
}
|
||||
|
||||
// Streamer represents a stream established with a client.
|
||||
// Stream represents a stream established with a client.
|
||||
// A stream can be bidirectional which is indicated by the request.
|
||||
// The last error will be left in Error().
|
||||
// EOF indicated end of the stream.
|
||||
type Streamer interface {
|
||||
// EOF indicates end of the stream.
|
||||
type Stream interface {
|
||||
Context() context.Context
|
||||
Request() Request
|
||||
Send(interface{}) error
|
||||
@@ -53,6 +57,34 @@ type Streamer interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Handler interface represents a request handler. It's generated
|
||||
// by passing any type of public concrete object with methods into server.NewHandler.
|
||||
// Most will pass in a struct.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Greeter struct {}
|
||||
//
|
||||
// func (g *Greeter) Hello(context, request, response) error {
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
type Handler interface {
|
||||
Name() string
|
||||
Handler() interface{}
|
||||
Endpoints() []*registry.Endpoint
|
||||
Options() HandlerOptions
|
||||
}
|
||||
|
||||
// Subscriber interface represents a subscription to a given topic using
|
||||
// a specific subscriber function or object with methods.
|
||||
type Subscriber interface {
|
||||
Topic() string
|
||||
Subscriber() interface{}
|
||||
Endpoints() []*registry.Endpoint
|
||||
Options() SubscriberOptions
|
||||
}
|
||||
|
||||
type Option func(*Options)
|
||||
|
||||
type HandlerOption func(*HandlerOptions)
|
||||
@@ -63,7 +95,7 @@ var (
|
||||
DefaultAddress = ":0"
|
||||
DefaultName = "go-server"
|
||||
DefaultVersion = "1.0.0"
|
||||
DefaultId = uuid.NewUUID().String()
|
||||
DefaultId = uuid.New().String()
|
||||
DefaultServer Server = newRpcServer()
|
||||
)
|
||||
|
||||
|
@@ -2,14 +2,15 @@ package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/micro/go-micro/broker"
|
||||
"github.com/micro/go-micro/codec"
|
||||
"github.com/micro/go-micro/metadata"
|
||||
"github.com/micro/go-micro/registry"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -176,6 +177,8 @@ func (s *rpcServer) createSubHandler(sb *subscriber, opts Options) broker.Handle
|
||||
delete(hdr, "Content-Type")
|
||||
ctx := metadata.NewContext(context.Background(), hdr)
|
||||
|
||||
results := make(chan error, len(sb.handlers))
|
||||
|
||||
for i := 0; i < len(sb.handlers); i++ {
|
||||
handler := sb.handlers[i]
|
||||
|
||||
@@ -204,7 +207,7 @@ func (s *rpcServer) createSubHandler(sb *subscriber, opts Options) broker.Handle
|
||||
return err
|
||||
}
|
||||
|
||||
fn := func(ctx context.Context, msg Publication) error {
|
||||
fn := func(ctx context.Context, msg Message) error {
|
||||
var vals []reflect.Value
|
||||
if sb.typ.Kind() != reflect.Func {
|
||||
vals = append(vals, sb.rcvr)
|
||||
@@ -213,7 +216,7 @@ func (s *rpcServer) createSubHandler(sb *subscriber, opts Options) broker.Handle
|
||||
vals = append(vals, reflect.ValueOf(ctx))
|
||||
}
|
||||
|
||||
vals = append(vals, reflect.ValueOf(msg.Message()))
|
||||
vals = append(vals, reflect.ValueOf(msg.Payload()))
|
||||
|
||||
returnValues := handler.method.Call(vals)
|
||||
if err := returnValues[0].Interface(); err != nil {
|
||||
@@ -229,13 +232,26 @@ func (s *rpcServer) createSubHandler(sb *subscriber, opts Options) broker.Handle
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
fn(ctx, &rpcPublication{
|
||||
results <- fn(ctx, &rpcMessage{
|
||||
topic: sb.topic,
|
||||
contentType: ct,
|
||||
message: req.Interface(),
|
||||
payload: req.Interface(),
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
var errors []string
|
||||
|
||||
for i := 0; i < len(sb.handlers); i++ {
|
||||
if err := <-results; err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"context"
|
||||
)
|
||||
|
||||
// HandlerFunc represents a single method of a handler. It's used primarily
|
||||
@@ -12,7 +12,7 @@ type HandlerFunc func(ctx context.Context, req Request, rsp interface{}) error
|
||||
// SubscriberFunc represents a single method of a subscriber. It's used primarily
|
||||
// for the wrappers. What's handed to the actual method is the concrete
|
||||
// publication message.
|
||||
type SubscriberFunc func(ctx context.Context, msg Publication) error
|
||||
type SubscriberFunc func(ctx context.Context, msg Message) error
|
||||
|
||||
// HandlerWrapper wraps the HandlerFunc and returns the equivalent
|
||||
type HandlerWrapper func(HandlerFunc) HandlerFunc
|
||||
@@ -20,8 +20,8 @@ type HandlerWrapper func(HandlerFunc) HandlerFunc
|
||||
// SubscriberWrapper wraps the SubscriberFunc and returns the equivalent
|
||||
type SubscriberWrapper func(SubscriberFunc) SubscriberFunc
|
||||
|
||||
// StreamerWrapper wraps a Streamer interface and returns the equivalent.
|
||||
// StreamWrapper wraps a Stream interface and returns the equivalent.
|
||||
// Because streams exist for the lifetime of a method invocation this
|
||||
// is a convenient way to wrap a Stream as its in use for trace, monitoring,
|
||||
// metrics, etc.
|
||||
type StreamerWrapper func(Streamer) Streamer
|
||||
type StreamWrapper func(Stream) Stream
|
27
service.go
27
service.go
@@ -7,7 +7,8 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
log "github.com/micro/go-log"
|
||||
"github.com/micro/cli"
|
||||
"github.com/micro/go-log"
|
||||
"github.com/micro/go-micro/client"
|
||||
"github.com/micro/go-micro/cmd"
|
||||
"github.com/micro/go-micro/metadata"
|
||||
@@ -66,8 +67,22 @@ func (s *service) Init(opts ...Option) {
|
||||
}
|
||||
|
||||
s.once.Do(func() {
|
||||
// save user action
|
||||
action := s.opts.Cmd.App().Action
|
||||
|
||||
// set service action
|
||||
s.opts.Cmd.App().Action = func(c *cli.Context) {
|
||||
// set register interval
|
||||
if i := time.Duration(c.GlobalInt("register_interval")); i > 0 {
|
||||
s.opts.RegisterInterval = i * time.Second
|
||||
}
|
||||
|
||||
// user action
|
||||
action(c)
|
||||
}
|
||||
|
||||
// Initialise the command flags, overriding new service
|
||||
s.opts.Cmd.Init(
|
||||
_ = s.opts.Cmd.Init(
|
||||
cmd.Broker(&s.opts.Broker),
|
||||
cmd.Registry(&s.opts.Registry),
|
||||
cmd.Transport(&s.opts.Transport),
|
||||
@@ -153,7 +168,7 @@ func (s *service) Run() error {
|
||||
go s.run(ex)
|
||||
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)
|
||||
signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)
|
||||
|
||||
select {
|
||||
// wait on kill signal
|
||||
@@ -165,9 +180,5 @@ func (s *service) Run() error {
|
||||
// exit reg loop
|
||||
close(ex)
|
||||
|
||||
if err := s.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return s.Stop()
|
||||
}
|
||||
|
@@ -1,13 +1,12 @@
|
||||
package micro
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/registry/mock"
|
||||
proto "github.com/micro/go-micro/server/debug/proto"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestService(t *testing.T) {
|
||||
@@ -31,9 +30,7 @@ func TestService(t *testing.T) {
|
||||
// we can't test service.Init as it parses the command line
|
||||
// service.Init()
|
||||
|
||||
// register handler
|
||||
// do that later
|
||||
|
||||
// run service
|
||||
go func() {
|
||||
// wait for start
|
||||
wg.Wait()
|
||||
@@ -60,6 +57,7 @@ func TestService(t *testing.T) {
|
||||
cancel()
|
||||
}()
|
||||
|
||||
// run service
|
||||
service.Run()
|
||||
if err := service.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,11 @@
|
||||
// Package http returns a http2 transport using net/http
|
||||
package http
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/transport"
|
||||
)
|
||||
|
||||
// NewTransport returns a new http transport using net/http and supporting http2
|
||||
func NewTransport(opts ...transport.Option) transport.Transport {
|
||||
return transport.NewTransport(opts...)
|
||||
}
|
||||
|
23
transport/http/options.go
Normal file
23
transport/http/options.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/micro/go-micro/transport"
|
||||
)
|
||||
|
||||
// Handle registers the handler for the given pattern.
|
||||
func Handle(pattern string, handler http.Handler) transport.Option {
|
||||
return func(o *transport.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
handlers, ok := o.Context.Value("http_handlers").(map[string]http.Handler)
|
||||
if !ok {
|
||||
handlers = make(map[string]http.Handler)
|
||||
}
|
||||
handlers[pattern] = handler
|
||||
o.Context = context.WithValue(o.Context, "http_handlers", handlers)
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
//"fmt"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
@@ -13,10 +14,11 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-log"
|
||||
maddr "github.com/micro/misc/lib/addr"
|
||||
mnet "github.com/micro/misc/lib/net"
|
||||
mls "github.com/micro/misc/lib/tls"
|
||||
"github.com/micro/h2c"
|
||||
maddr "github.com/micro/util/go/lib/addr"
|
||||
mnet "github.com/micro/util/go/lib/net"
|
||||
mls "github.com/micro/util/go/lib/tls"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
type buffer struct {
|
||||
@@ -38,16 +40,25 @@ type httpTransportClient struct {
|
||||
r chan *http.Request
|
||||
bl []*http.Request
|
||||
buff *bufio.Reader
|
||||
|
||||
// local/remote ip
|
||||
local string
|
||||
remote string
|
||||
}
|
||||
|
||||
type httpTransportSocket struct {
|
||||
ht *httpTransport
|
||||
r chan *http.Request
|
||||
conn net.Conn
|
||||
once sync.Once
|
||||
ht *httpTransport
|
||||
w http.ResponseWriter
|
||||
r *http.Request
|
||||
rw *bufio.ReadWriter
|
||||
|
||||
sync.Mutex
|
||||
buff *bufio.Reader
|
||||
conn net.Conn
|
||||
// for the first request
|
||||
ch chan *http.Request
|
||||
|
||||
// local/remote ip
|
||||
local string
|
||||
remote string
|
||||
}
|
||||
|
||||
type httpTransportListener struct {
|
||||
@@ -59,6 +70,14 @@ func (b *buffer) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *httpTransportClient) Local() string {
|
||||
return h.local
|
||||
}
|
||||
|
||||
func (h *httpTransportClient) Remote() string {
|
||||
return h.remote
|
||||
}
|
||||
|
||||
func (h *httpTransportClient) Send(m *Message) error {
|
||||
header := make(http.Header)
|
||||
|
||||
@@ -170,33 +189,87 @@ func (h *httpTransportClient) Close() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *httpTransportSocket) Local() string {
|
||||
return h.local
|
||||
}
|
||||
|
||||
func (h *httpTransportSocket) Remote() string {
|
||||
return h.remote
|
||||
}
|
||||
|
||||
func (h *httpTransportSocket) Recv(m *Message) error {
|
||||
if m == nil {
|
||||
return errors.New("message passed in is nil")
|
||||
}
|
||||
|
||||
// set timeout if its greater than 0
|
||||
if h.ht.opts.Timeout > time.Duration(0) {
|
||||
h.conn.SetDeadline(time.Now().Add(h.ht.opts.Timeout))
|
||||
}
|
||||
|
||||
r, err := http.ReadRequest(h.buff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Body.Close()
|
||||
m.Body = b
|
||||
|
||||
if m.Header == nil {
|
||||
m.Header = make(map[string]string)
|
||||
}
|
||||
|
||||
for k, v := range r.Header {
|
||||
// process http 1
|
||||
if h.r.ProtoMajor == 1 {
|
||||
// set timeout if its greater than 0
|
||||
if h.ht.opts.Timeout > time.Duration(0) {
|
||||
h.conn.SetDeadline(time.Now().Add(h.ht.opts.Timeout))
|
||||
}
|
||||
|
||||
var r *http.Request
|
||||
|
||||
select {
|
||||
// get first request
|
||||
case r = <-h.ch:
|
||||
// read next request
|
||||
default:
|
||||
rr, err := http.ReadRequest(h.rw.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r = rr
|
||||
}
|
||||
|
||||
// read body
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set body
|
||||
r.Body.Close()
|
||||
m.Body = b
|
||||
|
||||
// set headers
|
||||
for k, v := range r.Header {
|
||||
if len(v) > 0 {
|
||||
m.Header[k] = v[0]
|
||||
} else {
|
||||
m.Header[k] = ""
|
||||
}
|
||||
}
|
||||
|
||||
// return early early
|
||||
return nil
|
||||
}
|
||||
|
||||
// processing http2 request
|
||||
// read streaming body
|
||||
|
||||
// set max buffer size
|
||||
buf := make([]byte, 4*1024)
|
||||
|
||||
// read the request body
|
||||
n, err := h.r.Body.Read(buf)
|
||||
// not an eof error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if we have data
|
||||
if n > 0 {
|
||||
m.Body = buf[:n]
|
||||
}
|
||||
|
||||
// set headers
|
||||
for k, v := range h.r.Header {
|
||||
if len(v) > 0 {
|
||||
m.Header[k] = v[0]
|
||||
} else {
|
||||
@@ -204,78 +277,73 @@ func (h *httpTransportSocket) Recv(m *Message) error {
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case h.r <- r:
|
||||
default:
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *httpTransportSocket) Send(m *Message) error {
|
||||
b := bytes.NewBuffer(m.Body)
|
||||
defer b.Reset()
|
||||
if h.r.ProtoMajor == 1 {
|
||||
rsp := &http.Response{
|
||||
Header: h.r.Header,
|
||||
Body: ioutil.NopCloser(bytes.NewReader(m.Body)),
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
ContentLength: int64(len(m.Body)),
|
||||
}
|
||||
|
||||
r := <-h.r
|
||||
for k, v := range m.Header {
|
||||
rsp.Header.Set(k, v)
|
||||
}
|
||||
|
||||
rsp := &http.Response{
|
||||
Header: r.Header,
|
||||
Body: &buffer{b},
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
ContentLength: int64(len(m.Body)),
|
||||
// set timeout if its greater than 0
|
||||
if h.ht.opts.Timeout > time.Duration(0) {
|
||||
h.conn.SetDeadline(time.Now().Add(h.ht.opts.Timeout))
|
||||
}
|
||||
|
||||
return rsp.Write(h.conn)
|
||||
}
|
||||
|
||||
// http2 request
|
||||
|
||||
// set headers
|
||||
for k, v := range m.Header {
|
||||
rsp.Header.Set(k, v)
|
||||
h.w.Header().Set(k, v)
|
||||
}
|
||||
|
||||
select {
|
||||
case h.r <- r:
|
||||
default:
|
||||
}
|
||||
|
||||
// set timeout if its greater than 0
|
||||
if h.ht.opts.Timeout > time.Duration(0) {
|
||||
h.conn.SetDeadline(time.Now().Add(h.ht.opts.Timeout))
|
||||
}
|
||||
|
||||
return rsp.Write(h.conn)
|
||||
// write request
|
||||
_, err := h.w.Write(m.Body)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *httpTransportSocket) error(m *Message) error {
|
||||
b := bytes.NewBuffer(m.Body)
|
||||
defer b.Reset()
|
||||
rsp := &http.Response{
|
||||
Header: make(http.Header),
|
||||
Body: &buffer{b},
|
||||
Status: "500 Internal Server Error",
|
||||
StatusCode: 500,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
ContentLength: int64(len(m.Body)),
|
||||
}
|
||||
if h.r.ProtoMajor == 1 {
|
||||
rsp := &http.Response{
|
||||
Header: make(http.Header),
|
||||
Body: ioutil.NopCloser(bytes.NewReader(m.Body)),
|
||||
Status: "500 Internal Server Error",
|
||||
StatusCode: 500,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
ContentLength: int64(len(m.Body)),
|
||||
}
|
||||
|
||||
for k, v := range m.Header {
|
||||
rsp.Header.Set(k, v)
|
||||
}
|
||||
for k, v := range m.Header {
|
||||
rsp.Header.Set(k, v)
|
||||
}
|
||||
|
||||
return rsp.Write(h.conn)
|
||||
return rsp.Write(h.conn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *httpTransportSocket) Close() error {
|
||||
err := h.conn.Close()
|
||||
h.once.Do(func() {
|
||||
h.Lock()
|
||||
h.buff.Reset(nil)
|
||||
h.buff = nil
|
||||
h.Unlock()
|
||||
})
|
||||
return err
|
||||
if h.r.ProtoMajor == 1 {
|
||||
return h.conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *httpTransportListener) Addr() string {
|
||||
@@ -287,46 +355,81 @@ func (h *httpTransportListener) Close() error {
|
||||
}
|
||||
|
||||
func (h *httpTransportListener) Accept(fn func(Socket)) error {
|
||||
var tempDelay time.Duration
|
||||
// create handler mux
|
||||
mux := http.NewServeMux()
|
||||
|
||||
for {
|
||||
c, err := h.listener.Accept()
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok && ne.Temporary() {
|
||||
if tempDelay == 0 {
|
||||
tempDelay = 5 * time.Millisecond
|
||||
} else {
|
||||
tempDelay *= 2
|
||||
}
|
||||
if max := 1 * time.Second; tempDelay > max {
|
||||
tempDelay = max
|
||||
}
|
||||
log.Logf("http: Accept error: %v; retrying in %v\n", err, tempDelay)
|
||||
time.Sleep(tempDelay)
|
||||
continue
|
||||
// register our transport handler
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
var buf *bufio.ReadWriter
|
||||
var con net.Conn
|
||||
|
||||
// read a regular request
|
||||
if r.ProtoMajor == 1 {
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
return err
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(b))
|
||||
// hijack the conn
|
||||
hj, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
// we're screwed
|
||||
http.Error(w, "cannot serve conn", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
conn, bufrw, err := hj.Hijack()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
buf = bufrw
|
||||
con = conn
|
||||
}
|
||||
|
||||
sock := &httpTransportSocket{
|
||||
ht: h.ht,
|
||||
conn: c,
|
||||
buff: bufio.NewReader(c),
|
||||
r: make(chan *http.Request, 1),
|
||||
// save the request
|
||||
ch := make(chan *http.Request, 1)
|
||||
ch <- r
|
||||
|
||||
fn(&httpTransportSocket{
|
||||
ht: h.ht,
|
||||
w: w,
|
||||
r: r,
|
||||
rw: buf,
|
||||
ch: ch,
|
||||
conn: con,
|
||||
local: h.Addr(),
|
||||
remote: r.RemoteAddr,
|
||||
})
|
||||
})
|
||||
|
||||
// get optional handlers
|
||||
if h.ht.opts.Context != nil {
|
||||
handlers, ok := h.ht.opts.Context.Value("http_handlers").(map[string]http.Handler)
|
||||
if ok {
|
||||
for pattern, handler := range handlers {
|
||||
mux.Handle(pattern, handler)
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
// TODO: think of a better error response strategy
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Log("panic recovered: ", r)
|
||||
sock.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
fn(sock)
|
||||
}()
|
||||
}
|
||||
|
||||
// default http2 server
|
||||
srv := &http.Server{
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
// insecure connection use h2c
|
||||
if !(h.ht.opts.Secure || h.ht.opts.TLSConfig != nil) {
|
||||
srv.Handler = &h2c.HandlerH2C{
|
||||
Handler: mux,
|
||||
H2Server: &http2.Server{},
|
||||
}
|
||||
}
|
||||
|
||||
// begin serving
|
||||
return srv.Serve(h.listener)
|
||||
}
|
||||
|
||||
func (h *httpTransport) Dial(addr string, opts ...DialOption) (Client, error) {
|
||||
@@ -365,6 +468,8 @@ func (h *httpTransport) Dial(addr string, opts ...DialOption) (Client, error) {
|
||||
buff: bufio.NewReader(conn),
|
||||
dialOpts: dopts,
|
||||
r: make(chan *http.Request, 1),
|
||||
local: conn.LocalAddr().String(),
|
||||
remote: conn.RemoteAddr().String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -423,6 +528,17 @@ func (h *httpTransport) Listen(addr string, opts ...ListenOption) (Listener, err
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *httpTransport) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&h.opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *httpTransport) Options() Options {
|
||||
return h.opts
|
||||
}
|
||||
|
||||
func (h *httpTransport) String() string {
|
||||
return "http"
|
||||
}
|
||||
|
@@ -18,6 +18,9 @@ type mockSocket struct {
|
||||
exit chan bool
|
||||
// listener exit
|
||||
lexit chan bool
|
||||
|
||||
local string
|
||||
remote string
|
||||
}
|
||||
|
||||
type mockClient struct {
|
||||
@@ -51,6 +54,14 @@ func (ms *mockSocket) Recv(m *transport.Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *mockSocket) Local() string {
|
||||
return ms.local
|
||||
}
|
||||
|
||||
func (ms *mockSocket) Remote() string {
|
||||
return ms.remote
|
||||
}
|
||||
|
||||
func (ms *mockSocket) Send(m *transport.Message) error {
|
||||
select {
|
||||
case <-ms.exit:
|
||||
@@ -93,10 +104,12 @@ func (m *mockListener) Accept(fn func(transport.Socket)) error {
|
||||
return nil
|
||||
case c := <-m.conn:
|
||||
go fn(&mockSocket{
|
||||
lexit: c.lexit,
|
||||
exit: c.exit,
|
||||
send: c.recv,
|
||||
recv: c.send,
|
||||
lexit: c.lexit,
|
||||
exit: c.exit,
|
||||
send: c.recv,
|
||||
recv: c.send,
|
||||
local: c.Remote(),
|
||||
remote: c.Local(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -118,10 +131,12 @@ func (m *mockTransport) Dial(addr string, opts ...transport.DialOption) (transpo
|
||||
|
||||
client := &mockClient{
|
||||
&mockSocket{
|
||||
send: make(chan *transport.Message),
|
||||
recv: make(chan *transport.Message),
|
||||
exit: make(chan bool),
|
||||
lexit: listener.exit,
|
||||
send: make(chan *transport.Message),
|
||||
recv: make(chan *transport.Message),
|
||||
exit: make(chan bool),
|
||||
lexit: listener.exit,
|
||||
local: addr,
|
||||
remote: addr,
|
||||
},
|
||||
options,
|
||||
}
|
||||
@@ -171,6 +186,17 @@ func (m *mockTransport) Listen(addr string, opts ...transport.ListenOption) (tra
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
func (m *mockTransport) Init(opts ...transport.Option) error {
|
||||
for _, o := range opts {
|
||||
o(&m.opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockTransport) Options() transport.Options {
|
||||
return m.opts
|
||||
}
|
||||
|
||||
func (m *mockTransport) String() string {
|
||||
return "mock"
|
||||
}
|
||||
|
@@ -1,11 +1,11 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/transport/codec"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Package is an interface for synchronous communication
|
||||
// Package transport is an interface for synchronous communication
|
||||
package transport
|
||||
|
||||
import (
|
||||
@@ -14,6 +14,8 @@ type Socket interface {
|
||||
Recv(*Message) error
|
||||
Send(*Message) error
|
||||
Close() error
|
||||
Local() string
|
||||
Remote() string
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
@@ -30,6 +32,8 @@ type Listener interface {
|
||||
// services. It uses socket send/recv semantics and had various
|
||||
// implementations {HTTP, RabbitMQ, NATS, ...}
|
||||
type Transport interface {
|
||||
Init(...Option) error
|
||||
Options() Options
|
||||
Dial(addr string, opts ...DialOption) (Client, error)
|
||||
Listen(addr string, opts ...ListenOption) (Listener, error)
|
||||
String() string
|
||||
@@ -50,15 +54,3 @@ var (
|
||||
func NewTransport(opts ...Option) Transport {
|
||||
return newHTTPTransport(opts...)
|
||||
}
|
||||
|
||||
func Dial(addr string, opts ...DialOption) (Client, error) {
|
||||
return DefaultTransport.Dial(addr, opts...)
|
||||
}
|
||||
|
||||
func Listen(addr string, opts ...ListenOption) (Listener, error) {
|
||||
return DefaultTransport.Listen(addr, opts...)
|
||||
}
|
||||
|
||||
func String() string {
|
||||
return DefaultTransport.String()
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
package micro
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/client"
|
||||
"github.com/micro/go-micro/metadata"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type clientWrapper struct {
|
||||
@@ -36,12 +36,12 @@ func (c *clientWrapper) Call(ctx context.Context, req client.Request, rsp interf
|
||||
return c.Client.Call(ctx, req, rsp, opts...)
|
||||
}
|
||||
|
||||
func (c *clientWrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Streamer, error) {
|
||||
func (c *clientWrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
|
||||
ctx = c.setHeaders(ctx)
|
||||
return c.Client.Stream(ctx, req, opts...)
|
||||
}
|
||||
|
||||
func (c *clientWrapper) Publish(ctx context.Context, p client.Publication, opts ...client.PublishOption) error {
|
||||
func (c *clientWrapper) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
|
||||
ctx = c.setHeaders(ctx)
|
||||
return c.Client.Publish(ctx, p, opts...)
|
||||
}
|
||||
|
@@ -1,11 +1,10 @@
|
||||
package micro
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/metadata"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestWrapper(t *testing.T) {
|
||||
|
Reference in New Issue
Block a user