Compare commits
94 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
45420d8413 | ||
|
0dcea05fb8 | ||
|
b0b0338128 | ||
|
11d75dae1b | ||
|
fc0bbcd339 | ||
|
c82dadfa55 | ||
|
7c8d6087de | ||
|
1f03681d82 | ||
|
1c1d46e1ac | ||
|
a545091c36 | ||
|
ada9ef48cf | ||
|
a7c4afac54 | ||
|
043e4aa979 | ||
|
78da1fde94 | ||
|
e7104d609a | ||
|
1890ec7044 | ||
|
d48735793d | ||
|
6fb652f78a | ||
|
bd46e60c13 | ||
|
42235bc973 | ||
|
c07b3636c0 | ||
|
2f09d5830c | ||
|
48513c78b6 | ||
|
bd34d39401 | ||
|
59685a4ff9 | ||
|
6385bf743c | ||
|
8fd8d9bd35 | ||
|
53554d98cd | ||
|
f6165f35c0 | ||
|
ae3f59a2f5 | ||
|
0703c514a9 | ||
|
b92130eeee | ||
|
e0e4596be0 | ||
|
5f60f7518d | ||
|
f2d4226817 | ||
|
236cfd6a3b | ||
|
d970586a29 | ||
|
d29b5e2fab | ||
|
7f173dfc63 | ||
|
8be72b676d | ||
|
0e696f4907 | ||
|
1748328f14 | ||
|
eff39083ca | ||
|
e3f818d18e | ||
|
db0df07fd6 | ||
|
87c39542b0 | ||
|
fd01ead575 | ||
|
382abbf28b | ||
|
8264e90bce | ||
|
d4b149046f | ||
|
e0b3a48323 | ||
|
f4ea9787a9 | ||
|
9438fae607 |
@@ -1,7 +1,7 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.7.4
|
||||
- 1.8rc3
|
||||
- 1.9.5
|
||||
- 1.10.x
|
||||
notifications:
|
||||
slack:
|
||||
secure: aEvhLbhujaGaKSrOokiG3//PaVHTIrc3fBpoRbCRqfZpyq6WREoapJJhF+tIpWWOwaC9GmChbD6aHo/jMUgwKXVyPSaNjiEL87YzUUpL8B2zslNp1rgfTg/LrzthOx3Q1TYwpaAl3to0fuHUVFX4yMeC2vuThq7WSXgMMxFCtbc=
|
||||
|
355
README.md
355
README.md
@@ -1,24 +1,22 @@
|
||||
# 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 **microservices**. It is part of the [Micro](https://github.com/micro/micro) toolkit.
|
||||
Go Micro is a pluggable RPC framework for distributed systems 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.
|
||||
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.
|
||||
|
||||
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).
|
||||
Plugins are available at [github.com/micro/go-plugins](https://github.com/micro/go-plugins).
|
||||
|
||||
Follow us on Twitter at [@MicroHQ](https://twitter.com/microhq), join the [Slack](https://micro-services.slack.com) community [here](http://slack.micro.mu/) or
|
||||
check out the [Mailing List](https://groups.google.com/forum/#!forum/microhq).
|
||||
Follow us on [Twitter](https://twitter.com/microhq) or join the [Slack](http://slack.micro.mu/) community.
|
||||
|
||||
## Features
|
||||
|
||||
Go Micro abstracts way the details of distributed systems. Here are the main features.
|
||||
Go Micro abstracts away the details of distributed systems. Here are the main features.
|
||||
|
||||
- **Service Discovery** - Applications are automatically registered with service discovery so they can find each other.
|
||||
- **Load Balancing** - Smart client side load balancing is used to balance requests between instances of a service.
|
||||
- **Synchronous Communication** - Request-response is provided as a bidirectional streaming transport layer.
|
||||
- **Asynchronous Communication** - Microservices should promote an event driven architecture. Publish and Subscribe semantics are built in.
|
||||
- **Message Encoding** - Micro services can encode requests in a number of encoding formats and seamlessly decode based on the Content-Type header.
|
||||
- **RPC Client/Server** - The client and server leverage the above features and provide a clean simple interface for building microservices.
|
||||
- **Service Discovery** - Automatic service registration and name resolution
|
||||
- **Load Balancing** - Client side load balancing built on discovery
|
||||
- **Message Encoding** - Dynamic encoding based on content-type with protobuf and json support
|
||||
- **Sync Streaming** - RPC based communication with support for bidirectional streaming
|
||||
- **Async Messaging** - Native PubSub messaging built in for event driven architectures
|
||||
|
||||
Go Micro supports both the Service and Function programming models. Read on to learn more.
|
||||
|
||||
@@ -28,70 +26,63 @@ For more detailed information on the architecture, installation and use of go-mi
|
||||
|
||||
## Learn By Example
|
||||
|
||||
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). The [**examples**](https://github.com/micro/examples) directory contains many more examples for using things such as middleware/wrappers, selector filters, pub/sub and code generation.
|
||||
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.
|
||||
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).
|
||||
|
||||
Check out the blog post to learn how to write go-micro services [https://micro.mu/blog/2016/03/28/go-micro.html](https://micro.mu/blog/2016/03/28/go-micro.html) or watch the talk from the [Golang UK Conf 2016](https://www.youtube.com/watch?v=xspaDovwk34).
|
||||
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.
|
||||
|
||||
This is a quick getting started guide with the greeter service example.
|
||||
## Getting started
|
||||
|
||||
### Prerequisites: Service Discovery
|
||||
- [Install Protobuf](#install-protobuf)
|
||||
- [Service Discovery](#service-discovery)
|
||||
- [Writing a Service](#writing-a-service)
|
||||
- [Writing a Function](#writing-a-function)
|
||||
- [Publish & Subscribe](#publish--subscribe)
|
||||
- [Plugins](#plugins)
|
||||
- [Wrappers](#wrappers)
|
||||
|
||||
There's just one prerequisite. We need a service discovery system to resolve service names to their address.
|
||||
The default discovery mechanism used in go-micro is Consul. Discovery is however pluggable so you can used
|
||||
etcd, kubernetes, zookeeper, etc. Plugins can be found in [micro/go-plugins](https://github.com/micro/go-plugins).
|
||||
## Install Protobuf
|
||||
|
||||
### Multicast DNS
|
||||
Protobuf is required for code generation
|
||||
|
||||
We can use multicast DNS with the built in MDNS registry for a zero dependency configuration.
|
||||
You'll need to install:
|
||||
|
||||
Just pass `--registry=mdns` to any command
|
||||
```
|
||||
$ go run main.go --registry=mdns
|
||||
```
|
||||
- [protoc-gen-micro](https://github.com/micro/protoc-gen-micro)
|
||||
|
||||
## Service Discovery
|
||||
|
||||
Service discovery is used to resolve service names to addresses.
|
||||
|
||||
### Consul
|
||||
|
||||
Alternatively we can use the default discovery system which is Consul.
|
||||
[Consul](https://www.consul.io/) is used as the default service discovery system.
|
||||
|
||||
**Mac OS**
|
||||
```
|
||||
brew install consul
|
||||
consul agent -dev
|
||||
```
|
||||
Discovery is pluggable. Find plugins for etcd, kubernetes, zookeeper and more in the [micro/go-plugins](https://github.com/micro/go-plugins) repo.
|
||||
|
||||
**Docker**
|
||||
```
|
||||
docker run consul
|
||||
```
|
||||
[Install guide](https://www.consul.io/intro/getting-started/install.html)
|
||||
|
||||
[Further installation instructions](https://www.consul.io/intro/getting-started/install.html)
|
||||
### Multicast DNS
|
||||
|
||||
### Run Service
|
||||
[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 get github.com/micro/examples/service && service
|
||||
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
|
||||
```
|
||||
|
||||
### Call Service
|
||||
```
|
||||
$ service --run_client
|
||||
Hello John
|
||||
MICRO_REGISTRY=mdns go run main.go
|
||||
```
|
||||
|
||||
## 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 so we utilised protobuf to define the handler and request/response.
|
||||
Here's a definition for the Greeter handler with the method Hello which takes a HelloRequest and HelloResponse both with one string arguments.
|
||||
One of the key requirements of microservices is strongly defined interfaces. Micro uses protobuf to achieve this.
|
||||
|
||||
`go-micro/examples/service/proto/greeter.proto`:
|
||||
Here we define the Greeter handler with the method Hello. It takes a HelloRequest and HelloResponse both with one string arguments.
|
||||
|
||||
```proto
|
||||
syntax = "proto3";
|
||||
@@ -109,40 +100,34 @@ message HelloResponse {
|
||||
}
|
||||
```
|
||||
|
||||
### Install protobuf
|
||||
### Generate the proto
|
||||
|
||||
We use a protobuf plugin for code generation. This is completely optional. Look at [examples/server](https://github.com/micro/examples/blob/master/server/main.go)
|
||||
and [examples/client](https://github.com/micro/examples/blob/master/client/main.go) for examples without code generation.
|
||||
After writing the proto definition we must compile it using protoc with the micro plugin.
|
||||
|
||||
```shell
|
||||
go get github.com/micro/protobuf/{proto,protoc-gen-go}
|
||||
protoc --proto_path=$GOPATH/src:. --micro_out=. --go_out=. path/to/greeter.proto
|
||||
```
|
||||
|
||||
There's still a need for proto compiler to generate Go stub code from our proto file. You can either use the micro fork above or the official repo `github.com/golang/protobuf`.
|
||||
### Write the service
|
||||
|
||||
### Compile the proto
|
||||
Below is the code for the greeter service.
|
||||
|
||||
```shell
|
||||
protoc -I$GOPATH/src --go_out=plugins=micro:$GOPATH/src \
|
||||
$GOPATH/src/github.com/micro/examples/service/proto/greeter.proto
|
||||
```
|
||||
It does the following:
|
||||
|
||||
### Define the service
|
||||
|
||||
Below is the code sample for the Greeter service. It basically implements the interface defined above for the Greeter handler,
|
||||
initialises the service, registers the handler and then runs itself. Simple as that.
|
||||
|
||||
`go-micro/examples/service/main.go`:
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
micro "github.com/micro/go-micro"
|
||||
proto "github.com/micro/examples/service/proto"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Greeter struct{}
|
||||
@@ -156,15 +141,9 @@ func main() {
|
||||
// Create a new service. Optionally include some options here.
|
||||
service := micro.NewService(
|
||||
micro.Name("greeter"),
|
||||
micro.Version("latest"),
|
||||
micro.Metadata(map[string]string{
|
||||
"type": "helloworld",
|
||||
}),
|
||||
)
|
||||
|
||||
// Init will parse the command line flags. Any flags set will
|
||||
// override the above settings. Options defined here will
|
||||
// override anything set on the command line.
|
||||
// Init will parse the command line flags.
|
||||
service.Init()
|
||||
|
||||
// Register handler
|
||||
@@ -180,6 +159,10 @@ func main() {
|
||||
### 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
|
||||
@@ -187,29 +170,29 @@ go run examples/service/main.go
|
||||
|
||||
### Define a client
|
||||
|
||||
Below is the client code to query the greeter service. Notice we're using the code generated client interface `proto.NewGreeterClient`.
|
||||
This reduces the amount of boiler plate code we need to write. The greeter client can be reused throughout the code if need be.
|
||||
Below is the client code to query the greeter service.
|
||||
|
||||
`client.go`
|
||||
The generated proto includes a greeter client to reduce boilerplate code.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"))
|
||||
service.Init()
|
||||
|
||||
// Create new greeter client
|
||||
greeter := proto.NewGreeterClient("greeter", service.Client())
|
||||
greeter := proto.GreeterServiceClient("greeter", service.Client())
|
||||
|
||||
// Call the greeter
|
||||
rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{Name: "John"})
|
||||
@@ -226,13 +209,18 @@ func main() {
|
||||
|
||||
```shell
|
||||
go run client.go
|
||||
```
|
||||
|
||||
Output
|
||||
```
|
||||
Hello John
|
||||
```
|
||||
|
||||
## Writing a Function
|
||||
|
||||
Go Micro includes the Function programming model. This is the notion of a one time executing Service which operates much like a service except exiting
|
||||
after completing a request. A function is defined much like a service and called in exactly the same way.
|
||||
Go Micro includes the Function programming model.
|
||||
|
||||
A Function is a one time executing Service which exits after completing a request.
|
||||
|
||||
### Defining a Function
|
||||
|
||||
@@ -240,9 +228,10 @@ after completing a request. A function is defined much like a service and called
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
proto "github.com/micro/examples/function/proto"
|
||||
"github.com/micro/go-micro"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Greeter struct{}
|
||||
@@ -255,7 +244,7 @@ func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto
|
||||
func main() {
|
||||
// create a new function
|
||||
fnc := micro.NewFunction(
|
||||
micro.Name("go.micro.fnc.greeter"),
|
||||
micro.Name("greeter"),
|
||||
)
|
||||
|
||||
// init the command line
|
||||
@@ -271,60 +260,46 @@ func main() {
|
||||
|
||||
It's that simple.
|
||||
|
||||
## How does it work?
|
||||
## Publish & Subscribe
|
||||
|
||||
<p align="center">
|
||||
<img src="go-micro.png" />
|
||||
</p>
|
||||
Go-micro has a built in message broker interface for event driven architectures.
|
||||
|
||||
Go Micro is a framework that addresses the fundamental requirements to write microservices.
|
||||
PubSub operates on the same protobuf generated messages as RPC. They are encoded/decoded automatically and sent via the broker.
|
||||
By default go-micro includes a point-to-point http broker but this can be swapped out via go-plugins.
|
||||
|
||||
Let's dig into the core components.
|
||||
|
||||
### Registry
|
||||
|
||||
The registry provides a service discovery mechanism to resolve names to addresses. It can be backed by consul, etcd, zookeeper, dns, gossip, etc.
|
||||
Services should register using the registry on startup and deregister on shutdown. Services can optionally provide an expiry TTL and reregister
|
||||
on an interval to ensure liveness and that the service is cleaned up if it dies.
|
||||
|
||||
### Selector
|
||||
|
||||
The selector is a load balancing abstraction which builds on the registry. It allows services to be "filtered" using filter functions and "selected"
|
||||
using a choice of algorithms such as random, roundrobin, leastconn, etc. The selector is leveraged by the Client when making requests. The client
|
||||
will use the selector rather than the registry as it provides that built in mechanism of load balancing.
|
||||
|
||||
### Transport
|
||||
|
||||
The transport is the interface for synchronous request/response communication between services. It's akin to the golang net package but provides
|
||||
a higher level abstraction which allows us to switch out communication mechanisms e.g http, rabbitmq, websockets, NATS. The transport also
|
||||
supports bidirectional streaming. This is powerful for client side push to the server.
|
||||
|
||||
### Broker
|
||||
|
||||
The broker provides an interface to a message broker for asynchronous pub/sub communication. This is one of the fundamental requirements of an event
|
||||
driven architecture and microservices. By default we use an inbox style point to point HTTP system to minimise the number of dependencies required
|
||||
to get started. However there are many message broker implementations available in go-plugins e.g RabbitMQ, NATS, NSQ, Google Cloud Pub Sub.
|
||||
|
||||
### Codec
|
||||
|
||||
The codec is used for encoding and decoding messages before transporting them across the wire. This could be json, protobuf, bson, msgpack, etc.
|
||||
Where this differs from most other codecs is that we actually support the RPC format here as well. So we have JSON-RPC, PROTO-RPC, BSON-RPC, etc.
|
||||
It separates encoding from the client/server and provides a powerful method for integrating other systems such as gRPC, Vanadium, etc.
|
||||
|
||||
### Server
|
||||
|
||||
The server is the building block for writing a service. Here you can name your service, register request handlers, add middeware, etc. The service
|
||||
builds on the above packages to provide a unified interface for serving requests. The built in server is an RPC system. In the future there maybe
|
||||
other implementations. The server also allows you to define multiple codecs to serve different encoded messages.
|
||||
|
||||
### Client
|
||||
|
||||
The client provides an interface to make requests to services. Again like the server, it builds on the other packages to provide a unified interface
|
||||
for finding services by name using the registry, load balancing using the selector, making synchronous requests with the transport and asynchronous
|
||||
messaging using the broker.
|
||||
### Publish
|
||||
|
||||
|
||||
The above components are combined at the top-level of micro as a **Service**.
|
||||
Create a new publisher with a `topic` name and service client
|
||||
|
||||
```go
|
||||
p := micro.NewPublisher("events", service.Client())
|
||||
```
|
||||
|
||||
Publish a proto message
|
||||
|
||||
```go
|
||||
p.Publish(context.TODO(), &proto.Event{Name: "event"})
|
||||
```
|
||||
|
||||
### Subscribe
|
||||
|
||||
Create a message handler. It's signature should be `func(context.Context, v interface{}) error`.
|
||||
|
||||
```go
|
||||
func ProcessEvent(ctx context.Context, event *proto.Event) error {
|
||||
fmt.Printf("Got event %+v\n", event)
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
Register the message handler with a `topic`
|
||||
|
||||
```go
|
||||
micro.RegisterSubscriber("events", ProcessEvent)
|
||||
```
|
||||
|
||||
See [examples/pubsub](https://github.com/micro/examples/tree/master/pubsub) for a complete example.
|
||||
|
||||
## Plugins
|
||||
|
||||
@@ -359,6 +334,118 @@ Flag usage of plugins
|
||||
service --registry=etcdv3 --transport=nats --broker=kafka
|
||||
```
|
||||
|
||||
### Plugin as option
|
||||
|
||||
Alternatively you can set the plugin as an option to a service
|
||||
|
||||
```go
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro"
|
||||
// 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"
|
||||
)
|
||||
|
||||
func main() {
|
||||
registry := etcdv3.NewRegistry()
|
||||
broker := kafka.NewBroker()
|
||||
transport := nats.NewTransport()
|
||||
|
||||
service := micro.NewService(
|
||||
micro.Name("greeter"),
|
||||
micro.Registry(registry),
|
||||
micro.Broker(broker),
|
||||
micro.Transport(transport),
|
||||
)
|
||||
|
||||
service.Init()
|
||||
service.Run()
|
||||
}
|
||||
```
|
||||
|
||||
### Write plugins
|
||||
|
||||
Plugins are a concept built on Go's interface. Each package maintains a high level interface abstraction.
|
||||
Simply implement the interface and pass it in as an option to the service.
|
||||
|
||||
The service discovery interface is called [Registry](https://godoc.org/github.com/micro/go-micro/registry#Registry).
|
||||
Anything which implements this interface can be used as a registry. The same applies to the other packages.
|
||||
|
||||
```go
|
||||
type Registry interface {
|
||||
Register(*Service, ...RegisterOption) error
|
||||
Deregister(*Service) error
|
||||
GetService(string) ([]*Service, error)
|
||||
ListServices() ([]*Service, error)
|
||||
Watch() (Watcher, error)
|
||||
String() string
|
||||
}
|
||||
```
|
||||
|
||||
Browse [go-plugins](https://github.com/micro/go-plugins) to get a better idea of implementation details.
|
||||
|
||||
## 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
|
||||
|
@@ -2,8 +2,6 @@
|
||||
package broker
|
||||
|
||||
// Broker is an interface used for asynchronous messaging.
|
||||
// Its an abstraction over various message brokers
|
||||
// {NATS, RabbitMQ, Kafka, ...}
|
||||
type Broker interface {
|
||||
Options() Options
|
||||
Address() string
|
||||
|
@@ -2,7 +2,9 @@ package broker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -18,25 +20,20 @@ import (
|
||||
|
||||
"github.com/micro/go-log"
|
||||
"github.com/micro/go-micro/broker/codec/json"
|
||||
"github.com/micro/go-micro/errors"
|
||||
merr "github.com/micro/go-micro/errors"
|
||||
"github.com/micro/go-micro/registry"
|
||||
maddr "github.com/micro/misc/lib/addr"
|
||||
mnet "github.com/micro/misc/lib/net"
|
||||
mls "github.com/micro/misc/lib/tls"
|
||||
"github.com/micro/go-rcache"
|
||||
maddr "github.com/micro/util/go/lib/addr"
|
||||
mnet "github.com/micro/util/go/lib/net"
|
||||
mls "github.com/micro/util/go/lib/tls"
|
||||
"github.com/pborman/uuid"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// HTTP Broker is a placeholder for actual message brokers.
|
||||
// This should not really be used in production but useful
|
||||
// in developer where you want zero dependencies.
|
||||
|
||||
// HTTP Broker is a point to point async broker
|
||||
type httpBroker struct {
|
||||
id string
|
||||
address string
|
||||
unsubscribe chan *httpSubscriber
|
||||
opts Options
|
||||
id string
|
||||
address string
|
||||
opts Options
|
||||
|
||||
mux *http.ServeMux
|
||||
|
||||
@@ -53,9 +50,9 @@ type httpSubscriber struct {
|
||||
opts SubscribeOptions
|
||||
id string
|
||||
topic string
|
||||
ch chan *httpSubscriber
|
||||
fn Handler
|
||||
svc *registry.Service
|
||||
hb *httpBroker
|
||||
}
|
||||
|
||||
type httpPublication struct {
|
||||
@@ -106,11 +103,13 @@ func newHttpBroker(opts ...Option) Broker {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
// set address
|
||||
addr := ":0"
|
||||
if len(options.Addrs) > 0 && len(options.Addrs[0]) > 0 {
|
||||
addr = options.Addrs[0]
|
||||
}
|
||||
|
||||
// get registry
|
||||
reg, ok := options.Context.Value(registryKey).(registry.Registry)
|
||||
if !ok {
|
||||
reg = registry.DefaultRegistry
|
||||
@@ -123,7 +122,6 @@ func newHttpBroker(opts ...Option) Broker {
|
||||
r: reg,
|
||||
c: &http.Client{Transport: newTransport(options.TLSConfig)},
|
||||
subscribers: make(map[string][]*httpSubscriber),
|
||||
unsubscribe: make(chan *httpSubscriber),
|
||||
exit: make(chan chan error),
|
||||
mux: http.NewServeMux(),
|
||||
}
|
||||
@@ -153,9 +151,41 @@ func (h *httpSubscriber) Topic() string {
|
||||
}
|
||||
|
||||
func (h *httpSubscriber) Unsubscribe() error {
|
||||
h.ch <- h
|
||||
// artificial delay
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
return h.hb.unsubscribe(h)
|
||||
}
|
||||
|
||||
func (h *httpBroker) subscribe(s *httpSubscriber) error {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
if err := h.r.Register(s.svc, registry.RegisterTTL(registerTTL)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.subscribers[s.topic] = append(h.subscribers[s.topic], s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *httpBroker) unsubscribe(s *httpSubscriber) error {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
var subscribers []*httpSubscriber
|
||||
|
||||
// look for subscriber
|
||||
for _, sub := range h.subscribers[s.topic] {
|
||||
// deregister and skip forward
|
||||
if sub.id == s.id {
|
||||
h.r.Deregister(sub.svc)
|
||||
continue
|
||||
}
|
||||
// keep subscriber
|
||||
subscribers = append(subscribers, sub)
|
||||
}
|
||||
|
||||
// set subscribers
|
||||
h.subscribers[s.topic] = subscribers
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -177,29 +207,75 @@ func (h *httpBroker) run(l net.Listener) {
|
||||
// received exit signal
|
||||
case ch := <-h.exit:
|
||||
ch <- l.Close()
|
||||
h.Lock()
|
||||
h.running = false
|
||||
h.Unlock()
|
||||
return
|
||||
// unsubscribe subscriber
|
||||
case subscriber := <-h.unsubscribe:
|
||||
h.Lock()
|
||||
var subscribers []*httpSubscriber
|
||||
for _, sub := range h.subscribers[subscriber.topic] {
|
||||
// deregister and skip forward
|
||||
if sub.id == subscriber.id {
|
||||
h.RLock()
|
||||
for _, subs := range h.subscribers {
|
||||
for _, sub := range subs {
|
||||
h.r.Deregister(sub.svc)
|
||||
continue
|
||||
}
|
||||
subscribers = append(subscribers, sub)
|
||||
}
|
||||
h.subscribers[subscriber.topic] = subscribers
|
||||
h.Unlock()
|
||||
h.RUnlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *httpBroker) start() error {
|
||||
func (h *httpBroker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "POST" {
|
||||
err := merr.BadRequest("go.micro.broker", "Method not allowed")
|
||||
http.Error(w, err.Error(), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
defer req.Body.Close()
|
||||
|
||||
req.ParseForm()
|
||||
|
||||
b, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
errr := merr.InternalServerError("go.micro.broker", "Error reading request body: %v", err)
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(errr.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var m *Message
|
||||
if err = h.opts.Codec.Unmarshal(b, &m); err != nil {
|
||||
errr := merr.InternalServerError("go.micro.broker", "Error parsing request body: %v", err)
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(errr.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
topic := m.Header[":topic"]
|
||||
delete(m.Header, ":topic")
|
||||
|
||||
if len(topic) == 0 {
|
||||
errr := merr.InternalServerError("go.micro.broker", "Topic not found")
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(errr.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
p := &httpPublication{m: m, t: topic}
|
||||
id := req.Form.Get("id")
|
||||
|
||||
h.RLock()
|
||||
for _, subscriber := range h.subscribers[topic] {
|
||||
if id == subscriber.id {
|
||||
// sub is sync; crufty rate limiting
|
||||
// so we don't hose the cpu
|
||||
subscriber.fn(p)
|
||||
}
|
||||
}
|
||||
h.RUnlock()
|
||||
}
|
||||
|
||||
func (h *httpBroker) Address() string {
|
||||
h.RLock()
|
||||
defer h.RUnlock()
|
||||
return h.address
|
||||
}
|
||||
|
||||
func (h *httpBroker) Connect() error {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
@@ -255,11 +331,20 @@ func (h *httpBroker) start() error {
|
||||
go http.Serve(l, h.mux)
|
||||
go h.run(l)
|
||||
|
||||
// get registry
|
||||
reg, ok := h.opts.Context.Value(registryKey).(registry.Registry)
|
||||
if !ok {
|
||||
reg = registry.DefaultRegistry
|
||||
}
|
||||
// set rcache
|
||||
h.r = rcache.New(reg)
|
||||
|
||||
// set running
|
||||
h.running = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *httpBroker) stop() error {
|
||||
func (h *httpBroker) Disconnect() error {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
@@ -267,76 +352,30 @@ func (h *httpBroker) stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// stop rcache
|
||||
rc, ok := h.r.(rcache.Cache)
|
||||
if ok {
|
||||
rc.Stop()
|
||||
}
|
||||
|
||||
// exit and return err
|
||||
ch := make(chan error)
|
||||
h.exit <- ch
|
||||
err := <-ch
|
||||
|
||||
// set not running
|
||||
h.running = false
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *httpBroker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "POST" {
|
||||
err := errors.BadRequest("go.micro.broker", "Method not allowed")
|
||||
http.Error(w, err.Error(), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
defer req.Body.Close()
|
||||
|
||||
req.ParseForm()
|
||||
|
||||
b, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
errr := errors.InternalServerError("go.micro.broker", "Error reading request body: %v", err)
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(errr.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var m *Message
|
||||
if err = h.opts.Codec.Unmarshal(b, &m); err != nil {
|
||||
errr := errors.InternalServerError("go.micro.broker", "Error parsing request body: %v", err)
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(errr.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
topic := m.Header[":topic"]
|
||||
delete(m.Header, ":topic")
|
||||
|
||||
if len(topic) == 0 {
|
||||
errr := errors.InternalServerError("go.micro.broker", "Topic not found")
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(errr.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
p := &httpPublication{m: m, t: topic}
|
||||
id := req.Form.Get("id")
|
||||
|
||||
h.RLock()
|
||||
for _, subscriber := range h.subscribers[topic] {
|
||||
if id == subscriber.id {
|
||||
// sub is sync; crufty rate limiting
|
||||
// so we don't hose the cpu
|
||||
subscriber.fn(p)
|
||||
}
|
||||
}
|
||||
h.RUnlock()
|
||||
}
|
||||
|
||||
func (h *httpBroker) Address() string {
|
||||
return h.address
|
||||
}
|
||||
|
||||
func (h *httpBroker) Connect() error {
|
||||
return h.start()
|
||||
}
|
||||
|
||||
func (h *httpBroker) Disconnect() error {
|
||||
return h.stop()
|
||||
}
|
||||
|
||||
func (h *httpBroker) Init(opts ...Option) error {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
if h.running {
|
||||
return errors.New("cannot init while connected")
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&h.opts)
|
||||
}
|
||||
@@ -345,12 +384,19 @@ func (h *httpBroker) Init(opts ...Option) error {
|
||||
h.id = "broker-" + uuid.NewUUID().String()
|
||||
}
|
||||
|
||||
// get registry
|
||||
reg, ok := h.opts.Context.Value(registryKey).(registry.Registry)
|
||||
if !ok {
|
||||
reg = registry.DefaultRegistry
|
||||
}
|
||||
|
||||
h.r = reg
|
||||
// get rcache
|
||||
if rc, ok := h.r.(rcache.Cache); ok {
|
||||
rc.Stop()
|
||||
}
|
||||
|
||||
// set registry
|
||||
h.r = rcache.New(reg)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -360,10 +406,13 @@ func (h *httpBroker) Options() Options {
|
||||
}
|
||||
|
||||
func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption) error {
|
||||
h.RLock()
|
||||
s, err := h.r.GetService("topic:" + topic)
|
||||
if err != nil {
|
||||
h.RUnlock()
|
||||
return err
|
||||
}
|
||||
h.RUnlock()
|
||||
|
||||
m := &Message{
|
||||
Header: make(map[string]string),
|
||||
@@ -381,7 +430,7 @@ func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption)
|
||||
return err
|
||||
}
|
||||
|
||||
fn := func(node *registry.Node, b []byte) {
|
||||
pub := func(node *registry.Node, b []byte) {
|
||||
scheme := "http"
|
||||
|
||||
// check if secure is added in metadata
|
||||
@@ -411,15 +460,14 @@ func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption)
|
||||
case broadcastVersion:
|
||||
for _, node := range service.Nodes {
|
||||
// publish async
|
||||
go fn(node, b)
|
||||
go pub(node, b)
|
||||
}
|
||||
|
||||
default:
|
||||
// select node to publish to
|
||||
node := service.Nodes[rand.Int()%len(service.Nodes)]
|
||||
|
||||
// publish async
|
||||
go fn(node, b)
|
||||
go pub(node, b)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,7 +475,7 @@ func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption)
|
||||
}
|
||||
|
||||
func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
|
||||
opt := newSubscribeOptions(opts...)
|
||||
options := newSubscribeOptions(opts...)
|
||||
|
||||
// parse address for host, port
|
||||
parts := strings.Split(h.Address(), ":")
|
||||
@@ -439,7 +487,8 @@ func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeO
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id := uuid.NewUUID().String()
|
||||
// create unique id
|
||||
id := h.id + "." + uuid.NewUUID().String()
|
||||
|
||||
var secure bool
|
||||
|
||||
@@ -449,7 +498,7 @@ func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeO
|
||||
|
||||
// register service
|
||||
node := ®istry.Node{
|
||||
Id: h.id + "." + id,
|
||||
Id: id,
|
||||
Address: addr,
|
||||
Port: port,
|
||||
Metadata: map[string]string{
|
||||
@@ -457,7 +506,8 @@ func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeO
|
||||
},
|
||||
}
|
||||
|
||||
version := opt.Queue
|
||||
// check for queue group or broadcast queue
|
||||
version := options.Queue
|
||||
if len(version) == 0 {
|
||||
version = broadcastVersion
|
||||
}
|
||||
@@ -468,22 +518,22 @@ func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeO
|
||||
Nodes: []*registry.Node{node},
|
||||
}
|
||||
|
||||
// generate subscriber
|
||||
subscriber := &httpSubscriber{
|
||||
opts: opt,
|
||||
id: h.id + "." + id,
|
||||
opts: options,
|
||||
hb: h,
|
||||
id: id,
|
||||
topic: topic,
|
||||
ch: h.unsubscribe,
|
||||
fn: handler,
|
||||
svc: service,
|
||||
}
|
||||
|
||||
if err := h.r.Register(service, registry.RegisterTTL(registerTTL)); err != nil {
|
||||
// subscribe now
|
||||
if err := h.subscribe(subscriber); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h.Lock()
|
||||
h.subscribers[topic] = append(h.subscribers[topic], subscriber)
|
||||
h.Unlock()
|
||||
// return the subscriber
|
||||
return subscriber, nil
|
||||
}
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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,11 +1,10 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestBackoff(t *testing.T) {
|
||||
|
@@ -2,9 +2,8 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Client is the interface used to make requests to services.
|
||||
|
@@ -1,41 +1,7 @@
|
||||
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"
|
||||
"context"
|
||||
)
|
||||
|
||||
// CallFunc represents the individual call func
|
||||
|
@@ -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,13 @@
|
||||
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 (
|
||||
|
@@ -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) {
|
||||
|
@@ -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 {
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package client
|
||||
|
||||
import "golang.org/x/net/context"
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
@@ -2,6 +2,7 @@ package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -12,14 +13,14 @@ import (
|
||||
"github.com/micro/go-micro/metadata"
|
||||
"github.com/micro/go-micro/selector"
|
||||
"github.com/micro/go-micro/transport"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type rpcClient struct {
|
||||
once sync.Once
|
||||
opts Options
|
||||
pool *pool
|
||||
seq uint64
|
||||
}
|
||||
|
||||
func newRpcClient(opt ...Option) Client {
|
||||
@@ -29,6 +30,7 @@ func newRpcClient(opt ...Option) Client {
|
||||
once: sync.Once{},
|
||||
opts: opts,
|
||||
pool: newPool(opts.PoolSize, opts.PoolTTL),
|
||||
seq: 0,
|
||||
}
|
||||
|
||||
c := Client(rc)
|
||||
@@ -85,11 +87,15 @@ func (r *rpcClient) call(ctx context.Context, address string, req Request, resp
|
||||
r.pool.release(address, c, grr)
|
||||
}()
|
||||
|
||||
seq := 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()
|
||||
|
||||
|
@@ -1,14 +1,13 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"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 TestCallWrapper(t *testing.T) {
|
||||
|
@@ -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(),
|
||||
|
20
cmd/cmd.go
20
cmd/cmd.go
@@ -70,13 +70,24 @@ var (
|
||||
cli.IntFlag{
|
||||
Name: "client_pool_size",
|
||||
EnvVar: "MICRO_CLIENT_POOL_SIZE",
|
||||
Usage: "Sets the client connection pool size. Default: 0",
|
||||
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_name",
|
||||
EnvVar: "MICRO_SERVER_NAME",
|
||||
@@ -132,6 +143,7 @@ var (
|
||||
Name: "selector",
|
||||
EnvVar: "MICRO_SELECTOR",
|
||||
Usage: "Selector used to pick nodes for querying",
|
||||
Value: "cache",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "server",
|
||||
@@ -181,7 +193,7 @@ var (
|
||||
defaultServer = "rpc"
|
||||
defaultBroker = "http"
|
||||
defaultRegistry = "consul"
|
||||
defaultSelector = "default"
|
||||
defaultSelector = "cache"
|
||||
defaultTransport = "http"
|
||||
)
|
||||
|
||||
@@ -368,6 +380,10 @@ 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 {
|
||||
clientOpts = append(clientOpts, client.Retries(r))
|
||||
|
@@ -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 {
|
||||
|
@@ -91,3 +91,13 @@ func InternalServerError(id, format string, a ...interface{}) error {
|
||||
Status: http.StatusText(500),
|
||||
}
|
||||
}
|
||||
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
package micro
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/server"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type function struct {
|
||||
|
@@ -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) {
|
||||
|
@@ -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{}
|
||||
@@ -45,7 +45,7 @@ var (
|
||||
HeaderPrefix = "X-Micro-"
|
||||
)
|
||||
|
||||
// NewService creates an returns a new Service based on the packages within.
|
||||
// NewService creates and returns a new Service based on the packages within.
|
||||
func NewService(opts ...Option) Service {
|
||||
return newService(opts...)
|
||||
}
|
||||
@@ -81,5 +81,5 @@ func RegisterHandler(s server.Server, h interface{}, opts ...server.HandlerOptio
|
||||
|
||||
// RegisterSubscriber is syntactic sugar for registering a subscriber
|
||||
func RegisterSubscriber(topic string, s server.Server, h interface{}, opts ...server.SubscriberOption) error {
|
||||
return s.Subscribe(s.NewSubscriber(topic, h))
|
||||
return s.Subscribe(s.NewSubscriber(topic, h, opts...))
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
10
options.go
10
options.go
@@ -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 {
|
||||
@@ -104,6 +103,13 @@ func Registry(r registry.Registry) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Selector sets the selector for the service client
|
||||
func Selector(s selector.Selector) Option {
|
||||
return func(o *Options) {
|
||||
o.Client.Init(client.Selector(s))
|
||||
}
|
||||
}
|
||||
|
||||
// Transport sets the transport for the service
|
||||
// and the underlying components
|
||||
func Transport(t transport.Transport) Option {
|
||||
|
@@ -1,8 +1,9 @@
|
||||
package micro
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/client"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type publisher struct {
|
||||
|
@@ -1,9 +1,11 @@
|
||||
package consul
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
consul "github.com/hashicorp/consul/api"
|
||||
"github.com/micro/go-micro/registry"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func Config(c *consul.Config) registry.Option {
|
||||
@@ -14,3 +16,22 @@ func Config(c *consul.Config) registry.Option {
|
||||
o.Context = context.WithValue(o.Context, "consul_config", c)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 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,7 +17,7 @@ import (
|
||||
type consulRegistry struct {
|
||||
Address string
|
||||
Client *consul.Client
|
||||
Options Options
|
||||
opts Options
|
||||
|
||||
sync.Mutex
|
||||
register map[string]uint64
|
||||
@@ -60,11 +60,6 @@ func newConsulRegistry(opts ...Option) Registry {
|
||||
}
|
||||
}
|
||||
|
||||
// set timeout
|
||||
if options.Timeout > 0 {
|
||||
config.HttpClient.Timeout = options.Timeout
|
||||
}
|
||||
|
||||
// check if there are any addrs
|
||||
if len(options.Addrs) > 0 {
|
||||
addr, port, err := net.SplitHostPort(options.Addrs[0])
|
||||
@@ -79,6 +74,10 @@ func newConsulRegistry(opts ...Option) Registry {
|
||||
|
||||
// requires secure connection?
|
||||
if options.Secure || options.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)
|
||||
@@ -87,10 +86,15 @@ func newConsulRegistry(opts ...Option) Registry {
|
||||
// create the client
|
||||
client, _ := consul.NewClient(config)
|
||||
|
||||
// set timeout
|
||||
if options.Timeout > 0 {
|
||||
config.HttpClient.Timeout = options.Timeout
|
||||
}
|
||||
|
||||
cr := &consulRegistry{
|
||||
Address: config.Address,
|
||||
Client: client,
|
||||
Options: options,
|
||||
opts: options,
|
||||
register: make(map[string]uint64),
|
||||
}
|
||||
|
||||
@@ -111,16 +115,39 @@ func (c *consulRegistry) Deregister(s *Service) error {
|
||||
return c.Client.Agent().ServiceDeregister(node.Id)
|
||||
}
|
||||
|
||||
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 (c *consulRegistry) Register(s *Service, opts ...RegisterOption) error {
|
||||
if len(s.Nodes) == 0 {
|
||||
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 {
|
||||
@@ -151,16 +178,19 @@ 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{
|
||||
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),
|
||||
@@ -207,18 +237,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]
|
||||
@@ -232,6 +262,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" {
|
||||
@@ -275,10 +306,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
|
||||
}
|
||||
|
@@ -41,7 +41,7 @@ func newMockServer(rg *mockRegistry, l net.Listener) error {
|
||||
}
|
||||
|
||||
func newConsulTestRegistry(r *mockRegistry) (*consulRegistry, func()) {
|
||||
l, err := net.Listen("tcp", ":0")
|
||||
l, err := net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
// blurgh?!!
|
||||
panic(err.Error())
|
||||
@@ -104,7 +104,11 @@ func TestConsul_GetService_WithHealthyServiceNodes(t *testing.T) {
|
||||
})
|
||||
defer cl()
|
||||
|
||||
svc, _ := cr.GetService("service-name")
|
||||
svc, err := cr.GetService("service-name")
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error", err)
|
||||
}
|
||||
|
||||
if exp, act := 1, len(svc); exp != act {
|
||||
t.Fatalf("Expected len of svc to be `%d`, got `%d`.", exp, act)
|
||||
}
|
||||
@@ -140,7 +144,11 @@ func TestConsul_GetService_WithUnhealthyServiceNode(t *testing.T) {
|
||||
})
|
||||
defer cl()
|
||||
|
||||
svc, _ := cr.GetService("service-name")
|
||||
svc, err := cr.GetService("service-name")
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error", err)
|
||||
}
|
||||
|
||||
if exp, act := 1, len(svc); exp != act {
|
||||
t.Fatalf("Expected len of svc to be `%d`, got `%d`.", exp, act)
|
||||
}
|
||||
@@ -176,7 +184,11 @@ func TestConsul_GetService_WithUnhealthyServiceNodes(t *testing.T) {
|
||||
})
|
||||
defer cl()
|
||||
|
||||
svc, _ := cr.GetService("service-name")
|
||||
svc, err := cr.GetService("service-name")
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error", err)
|
||||
}
|
||||
|
||||
if exp, act := 1, len(svc); exp != act {
|
||||
t.Fatalf("Expected len of svc to be `%d`, got `%d`.", exp, act)
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -297,8 +297,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{}),
|
||||
}
|
||||
@@ -316,6 +322,10 @@ func (m *mdnsRegistry) String() string {
|
||||
return "mdns"
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) Options() registry.Options {
|
||||
return m.opts
|
||||
}
|
||||
|
||||
func NewRegistry(opts ...registry.Option) registry.Registry {
|
||||
return newRegistry(opts...)
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -87,14 +87,22 @@ 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 (m *mockRegistry) Options() registry.Options {
|
||||
return registry.Options{}
|
||||
}
|
||||
|
||||
func NewRegistry() registry.Registry {
|
||||
m := &mockRegistry{Services: make(map[string][]*registry.Service)}
|
||||
m.init()
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -13,14 +13,17 @@ type Registry interface {
|
||||
Deregister(*Service) error
|
||||
GetService(string) ([]*Service, error)
|
||||
ListServices() ([]*Service, error)
|
||||
Watch() (Watcher, error)
|
||||
Watch(...WatchOption) (Watcher, error)
|
||||
String() string
|
||||
Options() Options
|
||||
}
|
||||
|
||||
type Option func(*Options)
|
||||
|
||||
type RegisterOption func(*RegisterOptions)
|
||||
|
||||
type WatchOption func(*WatchOptions)
|
||||
|
||||
var (
|
||||
DefaultRegistry = newConsulRegistry()
|
||||
|
||||
@@ -52,8 +55,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 {
|
||||
|
121
selector/cache/cache.go
vendored
121
selector/cache/cache.go
vendored
@@ -1,3 +1,4 @@
|
||||
// Package cache is a caching selector. It uses the registry watcher.
|
||||
package cache
|
||||
|
||||
import (
|
||||
@@ -9,11 +10,6 @@ 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
|
||||
@@ -23,6 +19,8 @@ type cacheSelector struct {
|
||||
cache map[string][]*registry.Service
|
||||
ttls map[string]time.Time
|
||||
|
||||
watched map[string]bool
|
||||
|
||||
// used to close or reload watcher
|
||||
reload chan bool
|
||||
exit chan bool
|
||||
@@ -86,30 +84,59 @@ func (c *cacheSelector) get(service string) ([]*registry.Service, error) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
// watch service if not watched
|
||||
if _, ok := c.watched[service]; !ok {
|
||||
go c.run(service)
|
||||
c.watched[service] = true
|
||||
}
|
||||
|
||||
// get does the actual request for a service
|
||||
// it also caches it
|
||||
get := func(service string) ([]*registry.Service, error) {
|
||||
// ask the registry
|
||||
services, err := c.so.Registry.GetService(service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// cache results
|
||||
c.set(service, c.cp(services))
|
||||
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)
|
||||
}
|
||||
|
||||
// got cache but lets check ttl
|
||||
ttl, kk := c.ttls[service]
|
||||
|
||||
// got results, copy and return
|
||||
if ok && len(services) > 0 {
|
||||
// only return if its less than the ttl
|
||||
if kk && time.Since(ttl) < c.ttl {
|
||||
return c.cp(services), nil
|
||||
}
|
||||
// within ttl so return cache
|
||||
if kk && time.Since(ttl) < c.ttl {
|
||||
return c.cp(services), nil
|
||||
}
|
||||
|
||||
// cache miss or ttl expired
|
||||
// expired entry so get service
|
||||
services, err := get(service)
|
||||
|
||||
// now ask the registry
|
||||
services, err := c.so.Registry.GetService(service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// no error then return error
|
||||
if err == nil {
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// we didn't have any results so cache
|
||||
c.cache[service] = c.cp(services)
|
||||
c.ttls[service] = time.Now().Add(c.ttl)
|
||||
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
|
||||
}
|
||||
|
||||
func (c *cacheSelector) set(service string, services []*registry.Service) {
|
||||
@@ -229,9 +256,7 @@ 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() {
|
||||
go c.tick()
|
||||
|
||||
func (c *cacheSelector) run(name string) {
|
||||
for {
|
||||
// exit early if already dead
|
||||
if c.quit() {
|
||||
@@ -239,8 +264,13 @@ 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
|
||||
}
|
||||
log.Log(err)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
@@ -248,33 +278,15 @@ func (c *cacheSelector) run() {
|
||||
|
||||
// watch for events
|
||||
if err := c.watch(w); err != nil {
|
||||
if c.quit() {
|
||||
return
|
||||
}
|
||||
log.Log(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check cache and expire on each tick
|
||||
func (c *cacheSelector) tick() {
|
||||
t := time.NewTicker(time.Minute)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
c.Lock()
|
||||
for service, expiry := range c.ttls {
|
||||
if d := time.Since(expiry); d > c.ttl {
|
||||
// TODO: maybe refresh the cache rather than blowing it away
|
||||
c.del(service)
|
||||
}
|
||||
}
|
||||
c.Unlock()
|
||||
case <-c.exit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// watch loops the next event and calls update
|
||||
// it returns if there's an error
|
||||
func (c *cacheSelector) watch(w registry.Watcher) error {
|
||||
@@ -365,6 +377,7 @@ func (c *cacheSelector) Reset(service string) {
|
||||
func (c *cacheSelector) Close() error {
|
||||
c.Lock()
|
||||
c.cache = make(map[string][]*registry.Service)
|
||||
c.watched = make(map[string]bool)
|
||||
c.Unlock()
|
||||
|
||||
select {
|
||||
@@ -401,15 +414,13 @@ func NewSelector(opts ...selector.Option) selector.Selector {
|
||||
}
|
||||
}
|
||||
|
||||
c := &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),
|
||||
return &cacheSelector{
|
||||
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),
|
||||
}
|
||||
|
||||
go c.run()
|
||||
return c
|
||||
}
|
||||
|
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{}
|
||||
|
@@ -1,9 +1,6 @@
|
||||
package selector
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/registry"
|
||||
)
|
||||
|
||||
@@ -11,10 +8,6 @@ type defaultSelector struct {
|
||||
so Options
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
}
|
||||
|
||||
func (r *defaultSelector) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&r.so)
|
||||
|
@@ -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,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 {
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/micro/go-micro/codec/jsonrpc"
|
||||
"github.com/micro/go-micro/codec/protorpc"
|
||||
"github.com/micro/go-micro/transport"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type rpcPlusCodec struct {
|
||||
@@ -96,7 +97,11 @@ func (c *rpcPlusCodec) WriteResponse(r *response, body interface{}, last bool) e
|
||||
Header: map[string]string{},
|
||||
}
|
||||
if err := c.codec.Write(m, body); err != nil {
|
||||
return err
|
||||
c.buf.wbuf.Reset()
|
||||
m.Error = errors.Wrapf(err, "Unable to encode body").Error()
|
||||
if err := c.codec.Write(m, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
m.Header["Content-Type"] = c.req.Header["Content-Type"]
|
||||
|
100
server/rpc_codec_test.go
Normal file
100
server/rpc_codec_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/codec"
|
||||
"github.com/micro/go-micro/transport"
|
||||
)
|
||||
|
||||
// testCodec is a dummy codec that only knows how to encode nil bodies
|
||||
type testCodec struct {
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
type testSocket struct {
|
||||
}
|
||||
|
||||
// TestCodecWriteError simulates what happens when a codec is unable
|
||||
// to encode a message (e.g. a missing branch of an "oneof" message in
|
||||
// protobufs)
|
||||
//
|
||||
// We expect an error to be sent to the socket. Previously the socket
|
||||
// would remain open with no bytes sent, leading to client-side
|
||||
// timeouts.
|
||||
func TestCodecWriteError(t *testing.T) {
|
||||
socket := testSocket{}
|
||||
message := transport.Message{
|
||||
Header: map[string]string{},
|
||||
Body: []byte{},
|
||||
}
|
||||
|
||||
rwc := readWriteCloser{
|
||||
rbuf: new(bytes.Buffer),
|
||||
wbuf: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
c := rpcPlusCodec{
|
||||
buf: &rwc,
|
||||
codec: &testCodec{
|
||||
buf: rwc.wbuf,
|
||||
},
|
||||
req: &message,
|
||||
socket: socket,
|
||||
}
|
||||
|
||||
err := c.WriteResponse(&response{
|
||||
ServiceMethod: "Service.Method",
|
||||
Seq: 0,
|
||||
Error: "",
|
||||
next: nil,
|
||||
}, "body", false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf(`Expected WriteResponse to fail; got "%+v" instead`, err)
|
||||
}
|
||||
|
||||
const expectedError = "Unable to encode body: simulating a codec write failure"
|
||||
actualError := rwc.wbuf.String()
|
||||
if actualError != expectedError {
|
||||
t.Fatalf(`Expected error "%+v" in the write buffer, got "%+v" instead`, expectedError, actualError)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *testCodec) ReadHeader(message *codec.Message, typ codec.MessageType) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *testCodec) ReadBody(dest interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *testCodec) Write(message *codec.Message, dest interface{}) error {
|
||||
if dest != nil {
|
||||
return errors.New("simulating a codec write failure")
|
||||
}
|
||||
c.buf.Write([]byte(message.Error))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *testCodec) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *testCodec) String() string {
|
||||
return "string"
|
||||
}
|
||||
|
||||
func (s testSocket) Recv(message *transport.Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s testSocket) Send(message *transport.Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s testSocket) Close() error {
|
||||
return nil
|
||||
}
|
@@ -1,8 +1,10 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -15,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 {
|
||||
@@ -181,12 +181,12 @@ func (s *rpcServer) Subscribe(sb Subscriber) error {
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
_, ok = s.subscribers[sub]
|
||||
if ok {
|
||||
return fmt.Errorf("subscriber %v already exists", s)
|
||||
}
|
||||
s.subscribers[sub] = nil
|
||||
s.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -232,19 +232,34 @@ func (s *rpcServer) Register() error {
|
||||
node.Metadata["registry"] = config.Registry.String()
|
||||
|
||||
s.RLock()
|
||||
var endpoints []*registry.Endpoint
|
||||
for _, e := range s.handlers {
|
||||
// Maps are ordered randomly, sort the keys for consistency
|
||||
var handlerList []string
|
||||
for n, e := range s.handlers {
|
||||
// Only advertise non internal handlers
|
||||
if !e.Options().Internal {
|
||||
endpoints = append(endpoints, e.Endpoints()...)
|
||||
handlerList = append(handlerList, n)
|
||||
}
|
||||
}
|
||||
for e, _ := range s.subscribers {
|
||||
sort.Strings(handlerList)
|
||||
|
||||
var subscriberList []*subscriber
|
||||
for e := range s.subscribers {
|
||||
// Only advertise non internal subscribers
|
||||
if !e.Options().Internal {
|
||||
endpoints = append(endpoints, e.Endpoints()...)
|
||||
subscriberList = append(subscriberList, e)
|
||||
}
|
||||
}
|
||||
sort.Slice(subscriberList, func(i, j int) bool {
|
||||
return subscriberList[i].topic > subscriberList[j].topic
|
||||
})
|
||||
|
||||
var endpoints []*registry.Endpoint
|
||||
for _, n := range handlerList {
|
||||
endpoints = append(endpoints, s.handlers[n].Endpoints()...)
|
||||
}
|
||||
for _, e := range subscriberList {
|
||||
endpoints = append(endpoints, e.Endpoints()...)
|
||||
}
|
||||
s.RUnlock()
|
||||
|
||||
service := ®istry.Service{
|
||||
|
@@ -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 (
|
||||
@@ -249,7 +249,10 @@ func (s *service) call(ctx context.Context, server *server, sending *sync.Mutex,
|
||||
errmsg = err.Error()
|
||||
}
|
||||
|
||||
server.sendResponse(sending, req, replyv.Interface(), codec, errmsg, true)
|
||||
err = server.sendResponse(sending, req, replyv.Interface(), codec, errmsg, true)
|
||||
if err != nil {
|
||||
log.Log("rpc call: unable to send response: ", err)
|
||||
}
|
||||
server.freeRequest(req)
|
||||
return
|
||||
}
|
||||
|
@@ -1,9 +1,8 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Implements the Streamer interface
|
||||
|
@@ -2,13 +2,13 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/micro/go-log"
|
||||
"github.com/pborman/uuid"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Server interface {
|
||||
|
@@ -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
|
||||
|
@@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
@@ -9,7 +10,6 @@ import (
|
||||
"github.com/micro/go-micro/codec"
|
||||
"github.com/micro/go-micro/metadata"
|
||||
"github.com/micro/go-micro/registry"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
|
54
service.go
54
service.go
@@ -3,9 +3,12 @@ package micro
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
@@ -15,7 +18,7 @@ import (
|
||||
type service struct {
|
||||
opts Options
|
||||
|
||||
init chan bool
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func newService(opts ...Option) Service {
|
||||
@@ -30,7 +33,6 @@ func newService(opts ...Option) Service {
|
||||
|
||||
return &service{
|
||||
opts: options,
|
||||
init: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +46,10 @@ func (s *service) run(exit chan bool) {
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
s.opts.Server.Register()
|
||||
err := s.opts.Server.Register()
|
||||
if err != nil {
|
||||
log.Log("service run Server.Register error: ", err)
|
||||
}
|
||||
case <-exit:
|
||||
t.Stop()
|
||||
return
|
||||
@@ -56,32 +61,35 @@ func (s *service) run(exit chan bool) {
|
||||
// which parses command line flags. cmd.Init is only called
|
||||
// on first Init.
|
||||
func (s *service) Init(opts ...Option) {
|
||||
// If <-s.init blocks, Init has not been called yet
|
||||
// so we can call cmd.Init once.
|
||||
select {
|
||||
case <-s.init:
|
||||
// only process options
|
||||
for _, o := range opts {
|
||||
o(&s.opts)
|
||||
}
|
||||
default:
|
||||
// close init
|
||||
close(s.init)
|
||||
// process options
|
||||
for _, o := range opts {
|
||||
o(&s.opts)
|
||||
}
|
||||
|
||||
// process options
|
||||
for _, o := range opts {
|
||||
o(&s.opts)
|
||||
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),
|
||||
cmd.Client(&s.opts.Client),
|
||||
cmd.Server(&s.opts.Server),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *service) Options() Options {
|
||||
@@ -160,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
|
||||
@@ -172,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,35 +30,30 @@ func TestService(t *testing.T) {
|
||||
// we can't test service.Init as it parses the command line
|
||||
// service.Init()
|
||||
|
||||
// register handler
|
||||
// do that later
|
||||
|
||||
go func() {
|
||||
// wait for start
|
||||
wg.Wait()
|
||||
|
||||
// test call debug
|
||||
req := service.Client().NewRequest(
|
||||
"test.service",
|
||||
"Debug.Health",
|
||||
new(proto.HealthRequest),
|
||||
)
|
||||
|
||||
rsp := new(proto.HealthResponse)
|
||||
|
||||
err := service.Client().Call(context.TODO(), req, rsp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if rsp.Status != "ok" {
|
||||
t.Fatalf("service response: %s", rsp.Status)
|
||||
}
|
||||
|
||||
// shutdown the service
|
||||
cancel()
|
||||
}()
|
||||
|
||||
// run service
|
||||
service.Run()
|
||||
go service.Run()
|
||||
|
||||
// wait for start
|
||||
wg.Wait()
|
||||
|
||||
// test call debug
|
||||
req := service.Client().NewRequest(
|
||||
"test.service",
|
||||
"Debug.Health",
|
||||
new(proto.HealthRequest),
|
||||
)
|
||||
|
||||
rsp := new(proto.HealthResponse)
|
||||
|
||||
err := service.Client().Call(context.TODO(), req, rsp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if rsp.Status != "ok" {
|
||||
t.Fatalf("service response: %s", rsp.Status)
|
||||
}
|
||||
|
||||
// shutdown the service
|
||||
cancel()
|
||||
}
|
||||
|
@@ -14,9 +14,9 @@ import (
|
||||
"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"
|
||||
maddr "github.com/micro/util/go/lib/addr"
|
||||
mnet "github.com/micro/util/go/lib/net"
|
||||
mls "github.com/micro/util/go/lib/tls"
|
||||
)
|
||||
|
||||
type buffer struct {
|
||||
|
@@ -32,7 +32,7 @@ func TestHTTPTransportPortRange(t *testing.T) {
|
||||
}
|
||||
expectedPort(t, "44445", lsn2)
|
||||
|
||||
lsn, err := tp.Listen(":0")
|
||||
lsn, err := tp.Listen("127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Errorf("Did not expect an error, got %s", err)
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func TestHTTPTransportPortRange(t *testing.T) {
|
||||
func TestHTTPTransportCommunication(t *testing.T) {
|
||||
tr := NewTransport()
|
||||
|
||||
l, err := tr.Listen(":0")
|
||||
l, err := tr.Listen("127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected listen err: %v", err)
|
||||
}
|
||||
@@ -111,7 +111,7 @@ func TestHTTPTransportCommunication(t *testing.T) {
|
||||
func TestHTTPTransportError(t *testing.T) {
|
||||
tr := NewTransport()
|
||||
|
||||
l, err := tr.Listen(":0")
|
||||
l, err := tr.Listen("127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected listen err: %v", err)
|
||||
}
|
||||
@@ -181,7 +181,7 @@ func TestHTTPTransportError(t *testing.T) {
|
||||
func TestHTTPTransportTimeout(t *testing.T) {
|
||||
tr := NewTransport(Timeout(time.Millisecond * 100))
|
||||
|
||||
l, err := tr.Listen(":0")
|
||||
l, err := tr.Listen("127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected listen err: %v", err)
|
||||
}
|
||||
|
@@ -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,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 {
|
||||
|
@@ -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