├── internal ├── template │ ├── module.go │ ├── plugin.go │ ├── docker.go │ ├── makefile.go │ ├── wrapper.go │ ├── proto.go │ ├── readme.go │ ├── html.go │ ├── main.go │ └── handler.go ├── version │ └── version.go ├── stats │ ├── writer.go │ ├── writer_test.go │ ├── stats_test.go │ ├── stats.go │ └── template.go ├── command │ ├── cli │ │ ├── sort.go │ │ └── command.go │ └── bot │ │ └── commands.go ├── usage │ ├── proto │ │ ├── usage.proto │ │ ├── usage.micro.go │ │ └── usage.pb.go │ ├── usage.go │ └── plugin.go ├── helper │ ├── helper_test.go │ └── helper.go └── handler │ ├── meta.go │ ├── rpc_test.go │ └── rpc.go ├── main.go ├── Dockerfile ├── .travis.yml ├── plugins.go ├── web ├── sort.go ├── plugin.go ├── proxy.go ├── web.go └── templates.go ├── bot ├── sort.go ├── proto │ ├── bot.proto │ ├── bot.micro.go │ └── bot.pb.go ├── plugin.go ├── bot_test.go └── bot.go ├── api ├── plugin.go ├── proto │ ├── api.proto │ ├── api.micro.go │ └── api.pb.go └── api.go ├── proxy ├── plugin.go └── proxy.go ├── service ├── plugin.go └── service.go ├── go.mod ├── cli ├── commands.go ├── helpers.go └── cli.go ├── .compose.yml ├── plugin ├── manager.go ├── options.go ├── plugin.go └── README.md ├── README.md ├── cmd └── cmd.go ├── new └── new.go └── LICENSE /internal/template/module.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | var ( 4 | Module = `module {{.Dir}}` 5 | ) 6 | -------------------------------------------------------------------------------- /internal/version/version.go: -------------------------------------------------------------------------------- 1 | // Package version 2 | package version 3 | 4 | const ( 5 | V = "1549376196832741000" 6 | ) 7 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/micro/micro/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Init() 9 | } 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | RUN apk add --update ca-certificates && \ 3 | rm -rf /var/cache/apk/* /tmp/* 4 | ADD micro /micro 5 | ADD web/webapp/dist /web/webapp/dist 6 | WORKDIR / 7 | ENTRYPOINT [ "/micro" ] 8 | -------------------------------------------------------------------------------- /internal/template/plugin.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | var ( 4 | Plugin = `package main 5 | {{if .Plugins}} 6 | import ({{range .Plugins}} 7 | _ "github.com/micro/go-plugins/{{.}}"{{end}} 8 | ){{end}} 9 | ` 10 | ) 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.11.x 4 | - 1.12.x 5 | notifications: 6 | slack: 7 | secure: hJZEXQA4WpiEopu0kjYLTm9I30ZkWPUyDQyZgpufHmLzW6XGayMOcYcI+dSVZl4a6lid4px9gAAnTUUA5NZoFcKrWPMfNX2BnIvW/wpm2yKrW5rdpIlnT07YefeFRUa2l47ifSnTwr+aPsfM0TjIQB2Ep8NsrtvmbaaE1xMXgI8= 8 | -------------------------------------------------------------------------------- /plugins.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // etcd v3 registry 5 | _ "github.com/micro/go-plugins/registry/etcdv3" 6 | // nats transport 7 | _ "github.com/micro/go-plugins/transport/nats" 8 | // kafka broker 9 | _ "github.com/micro/go-plugins/broker/nats" 10 | ) 11 | -------------------------------------------------------------------------------- /internal/stats/writer.go: -------------------------------------------------------------------------------- 1 | package stats 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type writer struct { 8 | http.ResponseWriter 9 | status int 10 | } 11 | 12 | func (w *writer) WriteHeader(code int) { 13 | w.ResponseWriter.WriteHeader(code) 14 | w.status = code 15 | } 16 | -------------------------------------------------------------------------------- /web/sort.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "github.com/micro/go-micro/registry" 5 | ) 6 | 7 | type sortedServices struct { 8 | services []*registry.Service 9 | } 10 | 11 | func (s sortedServices) Len() int { 12 | return len(s.services) 13 | } 14 | 15 | func (s sortedServices) Less(i, j int) bool { 16 | return s.services[i].Name < s.services[j].Name 17 | } 18 | 19 | func (s sortedServices) Swap(i, j int) { 20 | s.services[i], s.services[j] = s.services[j], s.services[i] 21 | } 22 | -------------------------------------------------------------------------------- /bot/sort.go: -------------------------------------------------------------------------------- 1 | package bot 2 | 3 | import ( 4 | "github.com/micro/go-bot/command" 5 | ) 6 | 7 | type sortedCommands struct { 8 | commands []command.Command 9 | } 10 | 11 | func (s sortedCommands) Len() int { 12 | return len(s.commands) 13 | } 14 | 15 | func (s sortedCommands) Less(i, j int) bool { 16 | return s.commands[i].String() < s.commands[j].String() 17 | } 18 | 19 | func (s sortedCommands) Swap(i, j int) { 20 | s.commands[i], s.commands[j] = s.commands[j], s.commands[i] 21 | } 22 | -------------------------------------------------------------------------------- /bot/proto/bot.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package go.micro.bot; 4 | 5 | service Command { 6 | rpc Help(HelpRequest) returns (HelpResponse) {}; 7 | rpc Exec(ExecRequest) returns (ExecResponse) {}; 8 | } 9 | 10 | message HelpRequest { 11 | } 12 | 13 | message HelpResponse { 14 | string usage = 1; 15 | string description = 2; 16 | } 17 | 18 | message ExecRequest { 19 | repeated string args = 1; 20 | } 21 | 22 | message ExecResponse { 23 | bytes result = 1; 24 | string error = 2; 25 | } 26 | -------------------------------------------------------------------------------- /internal/command/cli/sort.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "github.com/micro/go-micro/registry" 5 | ) 6 | 7 | type sortedServices struct { 8 | services []*registry.Service 9 | } 10 | 11 | func (s sortedServices) Len() int { 12 | return len(s.services) 13 | } 14 | 15 | func (s sortedServices) Less(i, j int) bool { 16 | return s.services[i].Name < s.services[j].Name 17 | } 18 | 19 | func (s sortedServices) Swap(i, j int) { 20 | s.services[i], s.services[j] = s.services[j], s.services[i] 21 | } 22 | -------------------------------------------------------------------------------- /internal/template/docker.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | var ( 4 | DockerFNC = `FROM alpine 5 | ADD {{.Alias}}-{{.Type}} /{{.Alias}}-{{.Type}} 6 | ENTRYPOINT [ "/{{.Alias}}-{{.Type}}" ] 7 | ` 8 | 9 | DockerSRV = `FROM alpine 10 | ADD {{.Alias}}-{{.Type}} /{{.Alias}}-{{.Type}} 11 | ENTRYPOINT [ "/{{.Alias}}-{{.Type}}" ] 12 | ` 13 | 14 | DockerWEB = `FROM alpine 15 | ADD html /html 16 | ADD {{.Alias}}-{{.Type}} /{{.Alias}}-{{.Type}} 17 | WORKDIR / 18 | ENTRYPOINT [ "/{{.Alias}}-{{.Type}}" ] 19 | ` 20 | ) 21 | -------------------------------------------------------------------------------- /internal/usage/proto/usage.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message Usage { 4 | // service name 5 | string service = 1; 6 | // version of service 7 | string version = 2; 8 | // unique service id 9 | string id = 3; 10 | // unix timestamp of report 11 | uint64 timestamp = 4; 12 | // window of report in seconds 13 | uint64 window = 5; 14 | // usage metrics 15 | Metrics metrics = 6; 16 | } 17 | 18 | message Metrics { 19 | // counts such as requests, services, etc 20 | map count = 1; 21 | } 22 | -------------------------------------------------------------------------------- /api/plugin.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/micro/micro/plugin" 7 | ) 8 | 9 | var ( 10 | defaultManager = plugin.NewManager() 11 | ) 12 | 13 | // Plugins lists the api plugins 14 | func Plugins() []plugin.Plugin { 15 | return defaultManager.Plugins() 16 | } 17 | 18 | // Register registers an api plugin 19 | func Register(pl plugin.Plugin) error { 20 | for _, p := range plugin.Plugins() { 21 | if p.String() == pl.String() { 22 | return fmt.Errorf("%s registered globally", pl.String()) 23 | } 24 | } 25 | return defaultManager.Register(pl) 26 | } 27 | -------------------------------------------------------------------------------- /bot/plugin.go: -------------------------------------------------------------------------------- 1 | package bot 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/micro/micro/plugin" 7 | ) 8 | 9 | var ( 10 | defaultManager = plugin.NewManager() 11 | ) 12 | 13 | // Plugins lists the bot plugins 14 | func Plugins() []plugin.Plugin { 15 | return defaultManager.Plugins() 16 | } 17 | 18 | // Register registers an bot plugin 19 | func Register(pl plugin.Plugin) error { 20 | for _, p := range plugin.Plugins() { 21 | if p.String() == pl.String() { 22 | return fmt.Errorf("%s registered globally", pl.String()) 23 | } 24 | } 25 | return defaultManager.Register(pl) 26 | } 27 | -------------------------------------------------------------------------------- /internal/template/makefile.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | var ( 4 | Makefile = ` 5 | GOPATH:=$(shell go env GOPATH) 6 | 7 | {{if ne .Type "web"}} 8 | .PHONY: proto 9 | proto: 10 | protoc --proto_path=${GOPATH}/src:. --micro_out=. --go_out=. proto/example/example.proto 11 | 12 | .PHONY: build 13 | build: proto 14 | {{else}} 15 | .PHONY: build 16 | build: 17 | {{end}} 18 | go build -o {{.Alias}}-{{.Type}} main.go plugin.go 19 | 20 | .PHONY: test 21 | test: 22 | go test -v ./... -cover 23 | 24 | .PHONY: docker 25 | docker: 26 | docker build . -t {{.Alias}}-{{.Type}}:latest 27 | ` 28 | ) 29 | -------------------------------------------------------------------------------- /web/plugin.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/micro/micro/plugin" 7 | ) 8 | 9 | var ( 10 | defaultManager = plugin.NewManager() 11 | ) 12 | 13 | // Plugins lists the web plugins 14 | func Plugins() []plugin.Plugin { 15 | return defaultManager.Plugins() 16 | } 17 | 18 | // Register registers an web plugin 19 | func Register(pl plugin.Plugin) error { 20 | for _, p := range plugin.Plugins() { 21 | if p.String() == pl.String() { 22 | return fmt.Errorf("%s registered globally", pl.String()) 23 | } 24 | } 25 | return defaultManager.Register(pl) 26 | } 27 | -------------------------------------------------------------------------------- /proxy/plugin.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/micro/micro/plugin" 7 | ) 8 | 9 | var ( 10 | defaultManager = plugin.NewManager() 11 | ) 12 | 13 | // Plugins lists the sidecar plugins 14 | func Plugins() []plugin.Plugin { 15 | return defaultManager.Plugins() 16 | } 17 | 18 | // Register registers an sidecar plugin 19 | func Register(pl plugin.Plugin) error { 20 | for _, p := range plugin.Plugins() { 21 | if p.String() == pl.String() { 22 | return fmt.Errorf("%s registered globally", pl.String()) 23 | } 24 | } 25 | return defaultManager.Register(pl) 26 | } 27 | -------------------------------------------------------------------------------- /service/plugin.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/micro/micro/plugin" 7 | ) 8 | 9 | var ( 10 | defaultManager = plugin.NewManager() 11 | ) 12 | 13 | // Plugins lists the api plugins 14 | func Plugins() []plugin.Plugin { 15 | return defaultManager.Plugins() 16 | } 17 | 18 | // Register registers an api plugin 19 | func Register(pl plugin.Plugin) error { 20 | for _, p := range plugin.Plugins() { 21 | if p.String() == pl.String() { 22 | return fmt.Errorf("%s registered globally", pl.String()) 23 | } 24 | } 25 | return defaultManager.Register(pl) 26 | } 27 | -------------------------------------------------------------------------------- /api/proto/api.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package go.micro.api; 4 | 5 | message Pair { 6 | string key = 1; 7 | repeated string values = 2; 8 | } 9 | 10 | message Request { 11 | string method = 1; 12 | string path = 2; 13 | map header = 3; 14 | map get = 4; 15 | map post = 5; 16 | string body = 6; // raw request body; if not application/x-www-form-urlencoded 17 | string url = 7; // the full url 18 | } 19 | 20 | message Response { 21 | int32 statusCode = 1; 22 | map header = 2; 23 | string body = 3; 24 | } 25 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/micro/micro 2 | 3 | require ( 4 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e 5 | github.com/golang/protobuf v1.2.0 6 | github.com/google/uuid v1.1.0 7 | github.com/gorilla/mux v1.7.0 8 | github.com/micro/cli v0.1.0 9 | github.com/micro/go-api v0.7.0 10 | github.com/micro/go-bot v0.1.0 11 | github.com/micro/go-log v0.1.0 12 | github.com/micro/go-micro v0.27.0 13 | github.com/micro/go-plugins v0.25.0 14 | github.com/micro/go-proxy v0.2.0 15 | github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516 16 | github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca 17 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd 18 | google.golang.org/grpc v1.18.0 19 | ) 20 | -------------------------------------------------------------------------------- /cli/commands.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "sort" 7 | "text/tabwriter" 8 | 9 | "github.com/micro/cli" 10 | ) 11 | 12 | func quit(c *cli.Context, args []string) ([]byte, error) { 13 | os.Exit(0) 14 | return nil, nil 15 | } 16 | 17 | func help(c *cli.Context, args []string) ([]byte, error) { 18 | w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) 19 | 20 | fmt.Fprintln(os.Stdout, "Commands:") 21 | 22 | var keys []string 23 | for k, _ := range commands { 24 | keys = append(keys, k) 25 | } 26 | sort.Strings(keys) 27 | 28 | for _, k := range keys { 29 | cmd := commands[k] 30 | fmt.Fprintln(w, "\t", cmd.name, "\t\t", cmd.usage) 31 | } 32 | 33 | w.Flush() 34 | return nil, nil 35 | } 36 | -------------------------------------------------------------------------------- /internal/stats/writer_test.go: -------------------------------------------------------------------------------- 1 | package stats 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | ) 7 | 8 | type testResponseWriter struct { 9 | code int 10 | } 11 | 12 | func (w *testResponseWriter) Header() http.Header { 13 | return make(http.Header) 14 | } 15 | 16 | func (w *testResponseWriter) Write(b []byte) (int, error) { 17 | return 0, nil 18 | } 19 | 20 | func (w *testResponseWriter) WriteHeader(c int) { 21 | w.code = c 22 | } 23 | 24 | func TestWriter(t *testing.T) { 25 | tw := &testResponseWriter{} 26 | w := &writer{tw, 0} 27 | w.WriteHeader(200) 28 | 29 | if w.status != 200 { 30 | t.Fatalf("Expected status 200 got %d", w.status) 31 | } 32 | if tw.code != 200 { 33 | t.Fatalf("Expected status 200 got %d", tw.code) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.compose.yml: -------------------------------------------------------------------------------- 1 | consul: 2 | command: -server -bootstrap -rejoin 3 | image: progrium/consul:latest 4 | hostname: "registry" 5 | ports: 6 | - "8300:8300" 7 | - "8400:8400" 8 | - "8500:8500" 9 | - "8600:53/udp" 10 | api: 11 | command: --registry_address=registry:8500 --register_interval=5 --register_ttl=10 api 12 | build: . 13 | links: 14 | - consul 15 | ports: 16 | - "8080:8080" 17 | sidecar: 18 | command: --registry_address=registry:8500 --register_interval=5 --register_ttl=10 proxy 19 | build: . 20 | links: 21 | - consul 22 | ports: 23 | - "8081:8081" 24 | web: 25 | command: --registry_address=registry:8500 --register_interval=5 --register_ttl=10 web 26 | build: . 27 | links: 28 | - consul 29 | ports: 30 | - "8082:8082" 31 | -------------------------------------------------------------------------------- /internal/stats/stats_test.go: -------------------------------------------------------------------------------- 1 | package stats 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestStats(t *testing.T) { 8 | testCounters := []struct { 9 | c string 10 | i []int 11 | }{ 12 | { 13 | c: "test", 14 | i: []int{1, 10, 100}, 15 | }, 16 | } 17 | 18 | s := New() 19 | 20 | if err := s.Start(); err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | for _, tc := range testCounters { 25 | for _, i := range tc.i { 26 | s.Record(tc.c, i) 27 | } 28 | } 29 | 30 | if err := s.Stop(); err != nil { 31 | t.Fatal(err) 32 | } 33 | 34 | if len(s.Counters) == 0 { 35 | t.Fatalf("stats not recorded, counters are %+v", s.Counters) 36 | } 37 | 38 | for _, tc := range testCounters { 39 | if _, ok := s.Counters[0].Status[tc.c]; !ok { 40 | t.Fatalf("%s counter not found", tc.c) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /plugin/manager.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | type manager struct { 9 | sync.Mutex 10 | plugins []Plugin 11 | registered map[string]bool 12 | } 13 | 14 | var ( 15 | // global plugin manager 16 | defaultManager = newManager() 17 | ) 18 | 19 | func newManager() *manager { 20 | return &manager{ 21 | registered: make(map[string]bool), 22 | } 23 | } 24 | 25 | func (m *manager) Plugins() []Plugin { 26 | m.Lock() 27 | defer m.Unlock() 28 | return m.plugins 29 | } 30 | 31 | func (m *manager) Register(plugin Plugin) error { 32 | m.Lock() 33 | defer m.Unlock() 34 | 35 | name := plugin.String() 36 | 37 | if m.registered[name] { 38 | return fmt.Errorf("Plugin with name %s already registered", name) 39 | } 40 | 41 | m.registered[name] = true 42 | m.plugins = append(m.plugins, plugin) 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /internal/helper/helper_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/micro/go-micro/metadata" 8 | ) 9 | 10 | func TestRequestToContext(t *testing.T) { 11 | testData := []struct { 12 | request *http.Request 13 | expect metadata.Metadata 14 | }{ 15 | { 16 | &http.Request{ 17 | Header: http.Header{ 18 | "foo1": []string{"bar"}, 19 | "foo2": []string{"bar", "baz"}, 20 | }, 21 | }, 22 | metadata.Metadata{ 23 | "foo1": "bar", 24 | "foo2": "bar,baz", 25 | }, 26 | }, 27 | } 28 | 29 | for _, d := range testData { 30 | ctx := RequestToContext(d.request) 31 | md, ok := metadata.FromContext(ctx) 32 | if !ok { 33 | t.Fatalf("Expected metadata for request %+v", d.request) 34 | } 35 | for k, v := range d.expect { 36 | if val := md[k]; val != v { 37 | t.Fatalf("Expected %s for key %s for expected md %+v, got md %+v", v, k, d.expect, md) 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /api/proto/api.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: github.com/micro/micro/api/proto/api.proto 3 | 4 | /* 5 | Package go_micro_api is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | github.com/micro/micro/api/proto/api.proto 9 | 10 | It has these top-level messages: 11 | Pair 12 | Request 13 | Response 14 | */ 15 | package go_micro_api 16 | 17 | import proto "github.com/golang/protobuf/proto" 18 | import fmt "fmt" 19 | import math "math" 20 | 21 | // Reference imports to suppress errors if they are not otherwise used. 22 | var _ = proto.Marshal 23 | var _ = fmt.Errorf 24 | var _ = math.Inf 25 | 26 | // This is a compile-time assertion to ensure that this generated file 27 | // is compatible with the proto package it is being compiled against. 28 | // A compilation error at this line likely means your copy of the 29 | // proto package needs to be updated. 30 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 31 | -------------------------------------------------------------------------------- /internal/usage/proto/usage.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: github.com/micro/micro/internal/usage/proto/usage.proto 3 | 4 | /* 5 | Package usage is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | github.com/micro/micro/internal/usage/proto/usage.proto 9 | 10 | It has these top-level messages: 11 | Usage 12 | Metrics 13 | */ 14 | package usage 15 | 16 | import proto "github.com/golang/protobuf/proto" 17 | import fmt "fmt" 18 | import math "math" 19 | 20 | // Reference imports to suppress errors if they are not otherwise used. 21 | var _ = proto.Marshal 22 | var _ = fmt.Errorf 23 | var _ = math.Inf 24 | 25 | // This is a compile-time assertion to ensure that this generated file 26 | // is compatible with the proto package it is being compiled against. 27 | // A compilation error at this line likely means your copy of the 28 | // proto package needs to be updated. 29 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 30 | -------------------------------------------------------------------------------- /internal/template/wrapper.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | var ( 4 | WrapperAPI = `package client 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/micro/go-micro" 10 | "github.com/micro/go-micro/server" 11 | example "github.com/micro/examples/template/srv/proto/example" 12 | ) 13 | 14 | type exampleKey struct {} 15 | 16 | // FromContext retrieves the client from the Context 17 | func ExampleFromContext(ctx context.Context) (example.ExampleService, bool) { 18 | c, ok := ctx.Value(exampleKey{}).(example.ExampleService) 19 | return c, ok 20 | } 21 | 22 | // Client returns a wrapper for the ExampleClient 23 | func ExampleWrapper(service micro.Service) server.HandlerWrapper { 24 | client := example.NewExampleService("go.micro.srv.template", service.Client()) 25 | 26 | return func(fn server.HandlerFunc) server.HandlerFunc { 27 | return func(ctx context.Context, req server.Request, rsp interface{}) error { 28 | ctx = context.WithValue(ctx, exampleKey{}, client) 29 | return fn(ctx, req, rsp) 30 | } 31 | } 32 | } 33 | ` 34 | ) 35 | -------------------------------------------------------------------------------- /plugin/options.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "github.com/micro/cli" 5 | ) 6 | 7 | // Options are used as part of a new plugin 8 | type Options struct { 9 | Name string 10 | Flags []cli.Flag 11 | Commands []cli.Command 12 | Handlers []Handler 13 | Init func(*cli.Context) error 14 | } 15 | 16 | type Option func(o *Options) 17 | 18 | // WithFlag adds flags to a plugin 19 | func WithFlag(flag ...cli.Flag) Option { 20 | return func(o *Options) { 21 | o.Flags = append(o.Flags, flag...) 22 | } 23 | } 24 | 25 | // WithCommand adds commands to a plugin 26 | func WithCommand(cmd ...cli.Command) Option { 27 | return func(o *Options) { 28 | o.Commands = append(o.Commands, cmd...) 29 | } 30 | } 31 | 32 | // WithHandler adds middleware handlers to 33 | func WithHandler(h ...Handler) Option { 34 | return func(o *Options) { 35 | o.Handlers = append(o.Handlers, h...) 36 | } 37 | } 38 | 39 | // WithName defines the name of the plugin 40 | func WithName(n string) Option { 41 | return func(o *Options) { 42 | o.Name = n 43 | } 44 | } 45 | 46 | // WithInit sets the init function 47 | func WithInit(fn func(*cli.Context) error) Option { 48 | return func(o *Options) { 49 | o.Init = fn 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /internal/template/proto.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | var ( 4 | ProtoFNC = `syntax = "proto3"; 5 | 6 | package {{.FQDN}}; 7 | 8 | service Example { 9 | rpc Call(Request) returns (Response) {} 10 | } 11 | 12 | message Message { 13 | string say = 1; 14 | } 15 | 16 | message Request { 17 | string name = 1; 18 | } 19 | 20 | message Response { 21 | string msg = 1; 22 | } 23 | ` 24 | 25 | ProtoSRV = `syntax = "proto3"; 26 | 27 | package {{.FQDN}}; 28 | 29 | service Example { 30 | rpc Call(Request) returns (Response) {} 31 | rpc Stream(StreamingRequest) returns (stream StreamingResponse) {} 32 | rpc PingPong(stream Ping) returns (stream Pong) {} 33 | } 34 | 35 | message Message { 36 | string say = 1; 37 | } 38 | 39 | message Request { 40 | string name = 1; 41 | } 42 | 43 | message Response { 44 | string msg = 1; 45 | } 46 | 47 | message StreamingRequest { 48 | int64 count = 1; 49 | } 50 | 51 | message StreamingResponse { 52 | int64 count = 1; 53 | } 54 | 55 | message Ping { 56 | int64 stroke = 1; 57 | } 58 | 59 | message Pong { 60 | int64 stroke = 1; 61 | } 62 | ` 63 | 64 | ProtoAPI = `syntax = "proto3"; 65 | 66 | package {{.FQDN}}; 67 | 68 | import "github.com/micro/go-api/proto/api.proto"; 69 | 70 | service Example { 71 | rpc Call(go.api.Request) returns (go.api.Response) {} 72 | } 73 | ` 74 | ) 75 | -------------------------------------------------------------------------------- /internal/helper/helper.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "errors" 8 | "io/ioutil" 9 | "net/http" 10 | "strings" 11 | 12 | "github.com/micro/cli" 13 | "github.com/micro/go-micro/metadata" 14 | ) 15 | 16 | func ACMEHosts(ctx *cli.Context) []string { 17 | var hosts []string 18 | for _, host := range strings.Split(ctx.String("acme_hosts"), ",") { 19 | if len(host) > 0 { 20 | hosts = append(hosts, host) 21 | } 22 | } 23 | return hosts 24 | } 25 | 26 | func RequestToContext(r *http.Request) context.Context { 27 | ctx := context.Background() 28 | md := make(metadata.Metadata) 29 | for k, v := range r.Header { 30 | md[k] = strings.Join(v, ",") 31 | } 32 | return metadata.NewContext(ctx, md) 33 | } 34 | 35 | func TLSConfig(ctx *cli.Context) (*tls.Config, error) { 36 | cert := ctx.GlobalString("tls_cert_file") 37 | key := ctx.GlobalString("tls_key_file") 38 | ca := ctx.GlobalString("tls_client_ca_file") 39 | 40 | if len(cert) > 0 && len(key) > 0 { 41 | certs, err := tls.LoadX509KeyPair(cert, key) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | if len(ca) > 0 { 47 | caCert, err := ioutil.ReadFile(ca) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | caCertPool := x509.NewCertPool() 53 | caCertPool.AppendCertsFromPEM(caCert) 54 | 55 | return &tls.Config{ 56 | Certificates: []tls.Certificate{certs}, 57 | ClientCAs: caCertPool, 58 | ClientAuth: tls.RequireAndVerifyClientCert, 59 | }, nil 60 | } 61 | 62 | return &tls.Config{ 63 | Certificates: []tls.Certificate{certs}, 64 | }, nil 65 | } 66 | 67 | return nil, errors.New("TLS certificate and key files not specified") 68 | } 69 | -------------------------------------------------------------------------------- /internal/usage/usage.go: -------------------------------------------------------------------------------- 1 | // Package usage tracks micro usage 2 | package usage 3 | 4 | import ( 5 | "bytes" 6 | "crypto/sha256" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "net/http" 11 | "os" 12 | "time" 13 | 14 | "github.com/golang/protobuf/proto" 15 | "github.com/google/uuid" 16 | pb "github.com/micro/micro/internal/usage/proto" 17 | "github.com/micro/micro/internal/version" 18 | ) 19 | 20 | var ( 21 | // usage url 22 | u = "https://micro.mu/usage" 23 | // usage agent 24 | a = "micro/usage" 25 | // usage version 26 | v = version.V 27 | // 24 hour window 28 | w = 8.64e13 29 | ) 30 | 31 | // New generates a new usage report to be filled in 32 | func New(service string) *pb.Usage { 33 | id := fmt.Sprintf("micro.%s.%s.%s", service, version.V, uuid.New().String()) 34 | srv := "micro." + service 35 | sum := sha256.Sum256([]byte(id)) 36 | 37 | return &pb.Usage{ 38 | Service: srv, 39 | Version: v, 40 | Id: fmt.Sprintf("%x", sum), 41 | Timestamp: uint64(time.Now().UnixNano()), 42 | Window: uint64(w), 43 | Metrics: &pb.Metrics{ 44 | Count: make(map[string]uint64), 45 | }, 46 | } 47 | } 48 | 49 | // Report reports the current usage 50 | func Report(ug *pb.Usage) error { 51 | if v := os.Getenv("MICRO_REPORT_USAGE"); v == "false" { 52 | return nil 53 | } 54 | 55 | // update timestamp/window 56 | now := uint64(time.Now().UnixNano()) 57 | ug.Window = now - ug.Timestamp 58 | ug.Timestamp = now 59 | 60 | p, err := proto.Marshal(ug) 61 | if err != nil { 62 | return err 63 | } 64 | req, err := http.NewRequest("POST", u, bytes.NewReader(p)) 65 | if err != nil { 66 | return err 67 | } 68 | req.Header.Set("Content-Type", "application/protobuf") 69 | req.Header.Set("User-Agent", a) 70 | rsp, err := http.DefaultClient.Do(req) 71 | if err != nil { 72 | return err 73 | } 74 | defer rsp.Body.Close() 75 | io.Copy(ioutil.Discard, rsp.Body) 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /internal/handler/meta.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/micro/go-api/handler" 7 | "github.com/micro/go-api/handler/event" 8 | "github.com/micro/go-api/router" 9 | "github.com/micro/go-micro" 10 | "github.com/micro/go-micro/errors" 11 | 12 | // TODO: only import handler package 13 | aapi "github.com/micro/go-api/handler/api" 14 | ahttp "github.com/micro/go-api/handler/http" 15 | arpc "github.com/micro/go-api/handler/rpc" 16 | aweb "github.com/micro/go-api/handler/web" 17 | ) 18 | 19 | type metaHandler struct { 20 | s micro.Service 21 | r router.Router 22 | } 23 | 24 | func (m *metaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 25 | service, err := m.r.Route(r) 26 | if err != nil { 27 | er := errors.InternalServerError(m.r.Options().Namespace, err.Error()) 28 | w.Header().Set("Content-Type", "application/json") 29 | w.WriteHeader(500) 30 | w.Write([]byte(er.Error())) 31 | return 32 | } 33 | 34 | // TODO: don't do this ffs 35 | switch service.Endpoint.Handler { 36 | // web socket handler 37 | case aweb.Handler: 38 | aweb.WithService(service, handler.WithService(m.s)).ServeHTTP(w, r) 39 | // proxy handler 40 | case "proxy", ahttp.Handler: 41 | ahttp.WithService(service, handler.WithService(m.s)).ServeHTTP(w, r) 42 | // rpcx handler 43 | case arpc.Handler: 44 | arpc.WithService(service, handler.WithService(m.s)).ServeHTTP(w, r) 45 | // event handler 46 | case event.Handler: 47 | ev := event.NewHandler( 48 | handler.WithNamespace(m.r.Options().Namespace), 49 | handler.WithService(m.s), 50 | ) 51 | ev.ServeHTTP(w, r) 52 | // api handler 53 | case aapi.Handler: 54 | aapi.WithService(service, handler.WithService(m.s)).ServeHTTP(w, r) 55 | // default handler: rpc 56 | default: 57 | arpc.WithService(service, handler.WithService(m.s)).ServeHTTP(w, r) 58 | } 59 | } 60 | 61 | // Meta is a http.Handler that routes based on endpoint metadata 62 | func Meta(s micro.Service, r router.Router) http.Handler { 63 | return &metaHandler{ 64 | s: s, 65 | r: r, 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /web/proxy.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "net/http" 7 | "net/http/httputil" 8 | "strings" 9 | ) 10 | 11 | type proxy struct { 12 | Default *httputil.ReverseProxy 13 | Director func(r *http.Request) 14 | } 15 | 16 | func (p *proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { 17 | if !isWebSocket(r) { 18 | // the usual path 19 | p.Default.ServeHTTP(w, r) 20 | return 21 | } 22 | 23 | // the websocket path 24 | req := new(http.Request) 25 | *req = *r 26 | p.Director(req) 27 | host := req.URL.Host 28 | 29 | if len(host) == 0 { 30 | http.Error(w, "invalid host", 500) 31 | return 32 | } 33 | 34 | // set x-forward-for 35 | if clientIP, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { 36 | if ips, ok := req.Header["X-Forwarded-For"]; ok { 37 | clientIP = strings.Join(ips, ", ") + ", " + clientIP 38 | } 39 | req.Header.Set("X-Forwarded-For", clientIP) 40 | } 41 | 42 | // connect to the backend host 43 | conn, err := net.Dial("tcp", host) 44 | if err != nil { 45 | http.Error(w, err.Error(), 500) 46 | return 47 | } 48 | 49 | // hijack the connection 50 | hj, ok := w.(http.Hijacker) 51 | if !ok { 52 | http.Error(w, "failed to connect", 500) 53 | return 54 | } 55 | 56 | nc, _, err := hj.Hijack() 57 | if err != nil { 58 | return 59 | } 60 | 61 | defer nc.Close() 62 | defer conn.Close() 63 | 64 | if err = req.Write(conn); err != nil { 65 | return 66 | } 67 | 68 | errCh := make(chan error, 2) 69 | 70 | cp := func(dst io.Writer, src io.Reader) { 71 | _, err := io.Copy(dst, src) 72 | errCh <- err 73 | } 74 | 75 | go cp(conn, nc) 76 | go cp(nc, conn) 77 | 78 | <-errCh 79 | } 80 | 81 | func isWebSocket(r *http.Request) bool { 82 | contains := func(key, val string) bool { 83 | vv := strings.Split(r.Header.Get(key), ",") 84 | for _, v := range vv { 85 | if val == strings.ToLower(strings.TrimSpace(v)) { 86 | return true 87 | } 88 | } 89 | return false 90 | } 91 | 92 | if contains("Connection", "upgrade") && contains("Upgrade", "websocket") { 93 | return true 94 | } 95 | 96 | return false 97 | } 98 | -------------------------------------------------------------------------------- /internal/usage/plugin.go: -------------------------------------------------------------------------------- 1 | package usage 2 | 3 | import ( 4 | "math/rand" 5 | "net/http" 6 | "os" 7 | "sync/atomic" 8 | "time" 9 | 10 | "github.com/micro/cli" 11 | "github.com/micro/go-micro/registry" 12 | "github.com/micro/micro/plugin" 13 | ) 14 | 15 | func init() { 16 | plugin.Register(Plugin()) 17 | } 18 | 19 | func Plugin() plugin.Plugin { 20 | var requests uint64 21 | 22 | // create rand 23 | source := rand.NewSource(time.Now().UnixNano()) 24 | r := rand.New(source) 25 | 26 | return plugin.NewPlugin( 27 | plugin.WithName("usage"), 28 | plugin.WithInit(func(c *cli.Context) error { 29 | // only do if enabled 30 | if !c.GlobalBool("report_usage") { 31 | os.Setenv("MICRO_REPORT_USAGE", "false") 32 | return nil 33 | } 34 | 35 | if len(c.Args()) < 1 || len(c.Args()[0]) == 0 { 36 | return nil 37 | } 38 | 39 | // service name 40 | service := c.Args()[0] 41 | 42 | // kick off the tracker 43 | go func() { 44 | // new report 45 | u := New(service) 46 | 47 | // initial publish in 30-60 seconds 48 | d := 30 + r.Intn(30) 49 | time.Sleep(time.Second * time.Duration(d)) 50 | 51 | for { 52 | // get service list 53 | s, _ := registry.ListServices() 54 | // get requests 55 | reqs := atomic.LoadUint64(&requests) 56 | srvs := uint64(len(s)) 57 | 58 | // reset requests 59 | atomic.StoreUint64(&requests, 0) 60 | 61 | // set metrics 62 | u.Metrics.Count["requests"] = reqs 63 | u.Metrics.Count["services"] = srvs 64 | 65 | // send report 66 | Report(u) 67 | 68 | // now sleep 24 hours 69 | time.Sleep(time.Hour * 24) 70 | } 71 | }() 72 | 73 | return nil 74 | }), 75 | plugin.WithHandler(func(h http.Handler) http.Handler { 76 | // only enable if set 77 | if v := os.Getenv("MICRO_REPORT_USAGE"); v == "false" { 78 | return h 79 | } 80 | 81 | // return usage recorder 82 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 83 | // count requests 84 | atomic.AddUint64(&requests, 1) 85 | // serve the request 86 | h.ServeHTTP(w, r) 87 | }) 88 | }), 89 | ) 90 | } 91 | -------------------------------------------------------------------------------- /service/service.go: -------------------------------------------------------------------------------- 1 | // Package service provides a micro service 2 | package service 3 | 4 | import ( 5 | "strings" 6 | 7 | "github.com/micro/cli" 8 | "github.com/micro/go-micro" 9 | "github.com/micro/go-micro/server" 10 | "github.com/micro/go-proxy/router/http" 11 | "github.com/micro/go-proxy/router/mucp" 12 | ) 13 | 14 | func run(ctx *cli.Context, opts ...micro.Option) { 15 | name := ctx.String("name") 16 | address := ctx.String("address") 17 | endpoint := ctx.String("endpoint") 18 | 19 | if len(name) > 0 { 20 | opts = append(opts, micro.Name(name)) 21 | } else { 22 | name = server.DefaultName 23 | } 24 | 25 | if len(address) > 0 { 26 | opts = append(opts, micro.Address(address)) 27 | } 28 | 29 | switch { 30 | case strings.HasPrefix(endpoint, "http"): 31 | opts = append(opts, http.WithRouter(&http.Router{ 32 | Backend: endpoint, 33 | })) 34 | default: 35 | opts = append(opts, mucp.WithRouter(&mucp.Router{ 36 | Name: name, 37 | Backend: endpoint, 38 | })) 39 | } 40 | 41 | // new service 42 | service := micro.NewService(opts...) 43 | 44 | // run service 45 | service.Run() 46 | } 47 | 48 | func Commands(options ...micro.Option) []cli.Command { 49 | command := cli.Command{ 50 | Name: "service", 51 | Usage: "Run a micro service", 52 | Action: func(ctx *cli.Context) { 53 | run(ctx, options...) 54 | }, 55 | Flags: []cli.Flag{ 56 | cli.StringFlag{ 57 | Name: "name", 58 | Usage: "Name of the service", 59 | EnvVar: "MICRO_SERVICE_NAME", 60 | }, 61 | cli.StringFlag{ 62 | Name: "address", 63 | Usage: "Address of the service", 64 | EnvVar: "MICRO_SERVICE_ADDRESS", 65 | }, 66 | cli.StringFlag{ 67 | Name: "endpoint", 68 | Usage: "The local service endpoint", 69 | EnvVar: "MICRO_SERVICE_ENDPOINT", 70 | }, 71 | }, 72 | } 73 | 74 | for _, p := range Plugins() { 75 | if cmds := p.Commands(); len(cmds) > 0 { 76 | command.Subcommands = append(command.Subcommands, cmds...) 77 | } 78 | 79 | if flags := p.Flags(); len(flags) > 0 { 80 | command.Flags = append(command.Flags, flags...) 81 | } 82 | } 83 | 84 | return []cli.Command{command} 85 | } 86 | -------------------------------------------------------------------------------- /internal/template/readme.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | var ( 4 | Readme = `# {{title .Alias}} Service 5 | 6 | This is the {{title .Alias}} service 7 | 8 | Generated with 9 | 10 | ` + "```" + 11 | ` 12 | {{.Command}} 13 | ` + "```" + ` 14 | 15 | ## Getting Started 16 | 17 | - [Configuration](#configuration) 18 | - [Dependencies](#dependencies) 19 | - [Usage](#usage) 20 | 21 | ## Configuration 22 | 23 | - FQDN: {{.FQDN}} 24 | - Type: {{.Type}} 25 | - Alias: {{.Alias}} 26 | 27 | ## Dependencies 28 | 29 | Micro services depend on service discovery. The default is multicast DNS, a zeroconf system. 30 | 31 | In the event you need a resilient multi-host setup we recommend consul. 32 | 33 | ` + "```" + 34 | ` 35 | # install consul 36 | brew install consul 37 | 38 | # run consul 39 | consul agent -dev 40 | ` + "```" + ` 41 | 42 | ## Usage 43 | 44 | A Makefile is included for convenience 45 | 46 | Build the binary 47 | 48 | ` + "```" + 49 | ` 50 | make build 51 | ` + "```" + ` 52 | 53 | Run the service 54 | ` + "```" + 55 | ` 56 | ./{{.Alias}}-{{.Type}} 57 | ` + "```" + ` 58 | 59 | Build a docker image 60 | ` + "```" + 61 | ` 62 | make docker 63 | ` + "```" 64 | 65 | ReadmeFNC = `# {{title .Alias}} Function 66 | 67 | This is the {{title .Alias}} function 68 | 69 | Generated with 70 | 71 | ` + "```" + 72 | ` 73 | {{.Command}} 74 | ` + "```" + ` 75 | 76 | ## Getting Started 77 | 78 | - [Configuration](#configuration) 79 | - [Dependencies](#dependencies) 80 | - [Usage](#usage) 81 | 82 | ## Configuration 83 | 84 | - FQDN: {{.FQDN}} 85 | - Type: {{.Type}} 86 | - Alias: {{.Alias}} 87 | 88 | ## Dependencies 89 | 90 | Micro functions depend on service discovery. The default is consul. 91 | 92 | ` + "```" + 93 | ` 94 | # install consul 95 | brew install consul 96 | 97 | # run consul 98 | consul agent -dev 99 | ` + "```" + ` 100 | 101 | ## Usage 102 | 103 | A Makefile is included for convenience 104 | 105 | Build the binary 106 | 107 | ` + "```" + 108 | ` 109 | make build 110 | ` + "```" + ` 111 | 112 | Run the function once 113 | ` + "```" + 114 | ` 115 | ./{{.Alias}}-{{.Type}} 116 | ` + "```" + ` 117 | 118 | Build a docker image 119 | ` + "```" + 120 | ` 121 | make docker 122 | ` + "```" 123 | ) 124 | -------------------------------------------------------------------------------- /internal/handler/rpc_test.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "net/http" 9 | "net/http/httptest" 10 | "testing" 11 | 12 | "github.com/micro/go-micro/client" 13 | "github.com/micro/go-micro/cmd" 14 | "github.com/micro/go-micro/metadata" 15 | "github.com/micro/go-micro/registry/memory" 16 | "github.com/micro/go-micro/selector" 17 | "github.com/micro/go-micro/server" 18 | ) 19 | 20 | type TestHandler struct { 21 | t *testing.T 22 | expect metadata.Metadata 23 | } 24 | 25 | type TestRequest struct{} 26 | type TestResponse struct{} 27 | 28 | func (t *TestHandler) Exec(ctx context.Context, req *TestRequest, rsp *TestResponse) error { 29 | md, ok := metadata.FromContext(ctx) 30 | if !ok { 31 | return fmt.Errorf("Expected metadata got %t", ok) 32 | } 33 | 34 | for k, v := range t.expect { 35 | if val := md[k]; val != v { 36 | return fmt.Errorf("Expected %s for key %s got %s", v, k, val) 37 | } 38 | } 39 | 40 | t.t.Logf("Received request %+v", req) 41 | t.t.Logf("Received metadata %+v", md) 42 | 43 | return nil 44 | } 45 | 46 | func TestRPCHandler(t *testing.T) { 47 | r := memory.NewRegistry() 48 | 49 | (*cmd.DefaultOptions().Client).Init( 50 | client.Registry(r), 51 | client.Selector(selector.NewSelector(selector.Registry(r))), 52 | ) 53 | 54 | (*cmd.DefaultOptions().Server).Init( 55 | server.Name("test"), 56 | server.Registry(r), 57 | ) 58 | 59 | (*cmd.DefaultOptions().Server).Handle( 60 | (*cmd.DefaultOptions().Server).NewHandler(&TestHandler{t, metadata.Metadata{"Foo": "Bar"}}), 61 | ) 62 | 63 | if err := server.Start(); err != nil { 64 | t.Fatal(err) 65 | } 66 | 67 | w := httptest.NewRecorder() 68 | 69 | request := map[string]string{ 70 | "service": "test", 71 | "endpoint": "TestHandler.Exec", 72 | "request": "{}", 73 | } 74 | 75 | rb, err := json.Marshal(request) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | 80 | b := bytes.NewBuffer(rb) 81 | 82 | req, err := http.NewRequest("POST", "/rpc", b) 83 | if err != nil { 84 | t.Fatal(err) 85 | } 86 | req.Header.Set("Content-Type", "application/json") 87 | req.Header.Set("Foo", "Bar") 88 | 89 | RPC(w, req) 90 | 91 | if err := server.Stop(); err != nil { 92 | t.Fatal(err) 93 | } 94 | 95 | if w.Code != 200 { 96 | t.Fatalf("Expected 200 response got %d %s", w.Code, w.Body.String()) 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /plugin/plugin.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/micro/cli" 7 | ) 8 | 9 | // Plugin is the interface for plugins to micro. It differs from go-micro in that it's for 10 | // the micro API, Web, Sidecar, CLI. It's a method of building middleware for the HTTP side. 11 | type Plugin interface { 12 | // Global Flags 13 | Flags() []cli.Flag 14 | // Sub-commands 15 | Commands() []cli.Command 16 | // Handle is the middleware handler for HTTP requests. We pass in 17 | // the existing handler so it can be wrapped to create a call chain. 18 | Handler() Handler 19 | // Init called when command line args are parsed. 20 | // The initialised cli.Context is passed in. 21 | Init(*cli.Context) error 22 | // Name of the plugin 23 | String() string 24 | } 25 | 26 | // Manager is the plugin manager which stores plugins and allows them to be retrieved. 27 | // This is used by all the components of micro. 28 | type Manager interface { 29 | Plugins() []Plugin 30 | Register(Plugin) error 31 | } 32 | 33 | // Handler is the plugin middleware handler which wraps an existing http.Handler passed in. 34 | // Its the responsibility of the Handler to call the next http.Handler in the chain. 35 | type Handler func(http.Handler) http.Handler 36 | 37 | type plugin struct { 38 | opts Options 39 | init func(ctx *cli.Context) error 40 | handler Handler 41 | } 42 | 43 | func (p *plugin) Flags() []cli.Flag { 44 | return p.opts.Flags 45 | } 46 | 47 | func (p *plugin) Commands() []cli.Command { 48 | return p.opts.Commands 49 | } 50 | 51 | func (p *plugin) Handler() Handler { 52 | return p.handler 53 | } 54 | 55 | func (p *plugin) Init(ctx *cli.Context) error { 56 | return p.opts.Init(ctx) 57 | } 58 | 59 | func (p *plugin) String() string { 60 | return p.opts.Name 61 | } 62 | 63 | func newPlugin(opts ...Option) Plugin { 64 | options := Options{ 65 | Name: "default", 66 | Init: func(ctx *cli.Context) error { return nil }, 67 | } 68 | 69 | for _, o := range opts { 70 | o(&options) 71 | } 72 | 73 | handler := func(hdlr http.Handler) http.Handler { 74 | for _, h := range options.Handlers { 75 | hdlr = h(hdlr) 76 | } 77 | return hdlr 78 | } 79 | 80 | return &plugin{ 81 | opts: options, 82 | handler: handler, 83 | } 84 | } 85 | 86 | // Plugins lists the global plugins 87 | func Plugins() []Plugin { 88 | return defaultManager.Plugins() 89 | } 90 | 91 | // Register registers a global plugins 92 | func Register(plugin Plugin) error { 93 | return defaultManager.Register(plugin) 94 | } 95 | 96 | // NewManager creates a new plugin manager 97 | func NewManager() Manager { 98 | return newManager() 99 | } 100 | 101 | // NewPlugin makes it easy to create a new plugin 102 | func NewPlugin(opts ...Option) Plugin { 103 | return newPlugin(opts...) 104 | } 105 | -------------------------------------------------------------------------------- /plugin/README.md: -------------------------------------------------------------------------------- 1 | # Plugins 2 | 3 | Plugins are a way of integrating external code into the Micro toolkit. This is completely separate to go-micro plugins. 4 | Using plugins here allows you to add additional flags, commands and HTTP handlers to the toolkit. 5 | 6 | ## How it works 7 | 8 | There is a global plugin manager under micro/plugin which consists of plugins that will be used across the entire toolkit. 9 | Plugins can be registered by calling `plugin.Register`. Each component (api, web, sidecar, cli, bot) has a separate 10 | plugin manager used to register plugins which should only be added as part of that component. They can be used in 11 | the same way by called `api.Register`, `web.Register`, etc. 12 | 13 | Here's the interface 14 | 15 | ```go 16 | // Plugin is the interface for plugins to micro. It differs from go-micro in that it's for 17 | // the micro API, Web, Sidecar, CLI. It's a method of building middleware for the HTTP side. 18 | type Plugin interface { 19 | // Global Flags 20 | Flags() []cli.Flag 21 | // Sub-commands 22 | Commands() []cli.Command 23 | // Handle is the middleware handler for HTTP requests. We pass in 24 | // the existing handler so it can be wrapped to create a call chain. 25 | Handler() Handler 26 | // Init called when command line args are parsed. 27 | // The initialised cli.Context is passed in. 28 | Init(*cli.Context) error 29 | // Name of the plugin 30 | String() string 31 | } 32 | 33 | // Manager is the plugin manager which stores plugins and allows them to be retrieved. 34 | // This is used by all the components of micro. 35 | type Manager interface { 36 | Plugins() map[string]Plugin 37 | Register(name string, plugin Plugin) error 38 | } 39 | 40 | // Handler is the plugin middleware handler which wraps an existing http.Handler passed in. 41 | // Its the responsibility of the Handler to call the next http.Handler in the chain. 42 | type Handler func(http.Handler) http.Handler 43 | ``` 44 | 45 | ## How to use it 46 | 47 | Here's a simple example of a plugin that adds a flag and then prints the value 48 | 49 | ### The plugin 50 | 51 | Create a plugin.go file in the top level dir 52 | 53 | ```go 54 | package main 55 | 56 | import ( 57 | "log" 58 | "github.com/micro/cli" 59 | "github.com/micro/micro/plugin" 60 | ) 61 | 62 | func init() { 63 | plugin.Register(plugin.NewPlugin( 64 | plugin.WithName("example"), 65 | plugin.WithFlag(cli.StringFlag{ 66 | Name: "example_flag", 67 | Usage: "This is an example plugin flag", 68 | EnvVar: "EXAMPLE_FLAG", 69 | Value: "avalue", 70 | }), 71 | plugin.WithInit(func(ctx *cli.Context) error { 72 | log.Println("Got value for example_flag", ctx.String("example_flag")) 73 | return nil 74 | }), 75 | )) 76 | } 77 | ``` 78 | 79 | ### Building the code 80 | 81 | Simply build micro with the plugin 82 | 83 | ```shell 84 | go build -o micro ./main.go ./plugin.go 85 | ``` 86 | 87 | -------------------------------------------------------------------------------- /cli/helpers.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "os" 9 | "strings" 10 | "time" 11 | 12 | "github.com/micro/cli" 13 | "github.com/micro/go-micro/client" 14 | "github.com/micro/go-micro/cmd" 15 | clic "github.com/micro/micro/internal/command/cli" 16 | ) 17 | 18 | type exec func(*cli.Context, []string) ([]byte, error) 19 | 20 | func printer(e exec) func(*cli.Context) { 21 | return func(c *cli.Context) { 22 | rsp, err := e(c, c.Args()) 23 | if err != nil { 24 | fmt.Println(err) 25 | os.Exit(1) 26 | } 27 | fmt.Printf("%s\n", string(rsp)) 28 | } 29 | } 30 | 31 | func listServices(c *cli.Context, args []string) ([]byte, error) { 32 | return clic.ListServices(c) 33 | } 34 | 35 | func registerService(c *cli.Context, args []string) ([]byte, error) { 36 | return clic.RegisterService(c, args) 37 | } 38 | 39 | func deregisterService(c *cli.Context, args []string) ([]byte, error) { 40 | return clic.DeregisterService(c, args) 41 | } 42 | 43 | func getService(c *cli.Context, args []string) ([]byte, error) { 44 | return clic.GetService(c, args) 45 | } 46 | 47 | func callService(c *cli.Context, args []string) ([]byte, error) { 48 | return clic.CallService(c, args) 49 | } 50 | 51 | // TODO: stream via HTTP 52 | func streamService(c *cli.Context, args []string) ([]byte, error) { 53 | if len(args) < 2 { 54 | return nil, errors.New("require service and endpoint") 55 | } 56 | service := args[0] 57 | endpoint := args[1] 58 | var request map[string]interface{} 59 | err := json.Unmarshal([]byte(strings.Join(args[2:], " ")), &request) 60 | if err != nil { 61 | return nil, err 62 | } 63 | req := (*cmd.DefaultOptions().Client).NewRequest(service, endpoint, request, client.WithContentType("application/json")) 64 | stream, err := (*cmd.DefaultOptions().Client).Stream(context.Background(), req) 65 | if err != nil { 66 | return nil, fmt.Errorf("error calling %s.%s: %v", service, endpoint, err) 67 | } 68 | 69 | if err := stream.Send(request); err != nil { 70 | return nil, fmt.Errorf("error sending to %s.%s: %v", service, endpoint, err) 71 | } 72 | 73 | for { 74 | var response map[string]interface{} 75 | if err := stream.Recv(&response); err != nil { 76 | return nil, fmt.Errorf("error receiving from %s.%s: %v", service, endpoint, err) 77 | } 78 | 79 | b, _ := json.MarshalIndent(response, "", "\t") 80 | fmt.Print(string(b)) 81 | 82 | // artificial delay 83 | time.Sleep(time.Millisecond * 10) 84 | } 85 | } 86 | 87 | func publish(c *cli.Context, args []string) ([]byte, error) { 88 | if err := clic.Publish(c, args); err != nil { 89 | return nil, err 90 | } 91 | return []byte(`ok`), nil 92 | } 93 | 94 | func queryHealth(c *cli.Context, args []string) ([]byte, error) { 95 | return clic.QueryHealth(c, args) 96 | } 97 | 98 | func queryStats(c *cli.Context, args []string) ([]byte, error) { 99 | return clic.QueryStats(c, args) 100 | } 101 | -------------------------------------------------------------------------------- /bot/bot_test.go: -------------------------------------------------------------------------------- 1 | package bot 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "strings" 7 | "testing" 8 | "time" 9 | 10 | "github.com/micro/cli" 11 | "github.com/micro/go-bot/command" 12 | "github.com/micro/go-bot/input" 13 | 14 | "github.com/micro/go-micro" 15 | "github.com/micro/go-micro/registry/memory" 16 | ) 17 | 18 | type testInput struct { 19 | send chan *input.Event 20 | recv chan *input.Event 21 | exit chan bool 22 | } 23 | 24 | func (t *testInput) Flags() []cli.Flag { 25 | return nil 26 | } 27 | 28 | func (t *testInput) Init(*cli.Context) error { 29 | return nil 30 | } 31 | 32 | func (t *testInput) Close() error { 33 | select { 34 | case <-t.exit: 35 | default: 36 | close(t.exit) 37 | } 38 | return nil 39 | } 40 | 41 | func (t *testInput) Send(event *input.Event) error { 42 | if event == nil { 43 | return errors.New("nil event") 44 | } 45 | 46 | select { 47 | case <-t.exit: 48 | return errors.New("connection closed") 49 | case t.send <- event: 50 | return nil 51 | } 52 | } 53 | 54 | func (t *testInput) Recv(event *input.Event) error { 55 | if event == nil { 56 | return errors.New("nil event") 57 | } 58 | 59 | select { 60 | case <-t.exit: 61 | return errors.New("connection closed") 62 | case ev := <-t.recv: 63 | *event = *ev 64 | return nil 65 | } 66 | 67 | } 68 | 69 | func (t *testInput) Start() error { 70 | return nil 71 | } 72 | 73 | func (t *testInput) Stop() error { 74 | return nil 75 | } 76 | 77 | func (t *testInput) Stream() (input.Conn, error) { 78 | return t, nil 79 | } 80 | 81 | func (t *testInput) String() string { 82 | return "test" 83 | } 84 | 85 | func TestBot(t *testing.T) { 86 | flagSet := flag.NewFlagSet("test", flag.ExitOnError) 87 | app := cli.NewApp() 88 | ctx := cli.NewContext(app, flagSet, nil) 89 | 90 | io := &testInput{ 91 | send: make(chan *input.Event), 92 | recv: make(chan *input.Event), 93 | exit: make(chan bool), 94 | } 95 | 96 | inputs := map[string]input.Input{ 97 | "test": io, 98 | } 99 | 100 | commands := map[string]command.Command{ 101 | "^echo ": command.NewCommand("echo", "test usage", "test description", func(args ...string) ([]byte, error) { 102 | return []byte(strings.Join(args[1:], " ")), nil 103 | }), 104 | } 105 | 106 | service := micro.NewService( 107 | micro.Registry(memory.NewRegistry()), 108 | ) 109 | 110 | bot := newBot(ctx, inputs, commands, service) 111 | 112 | if err := bot.start(); err != nil { 113 | t.Fatal(err) 114 | } 115 | 116 | // send command 117 | select { 118 | case io.recv <- &input.Event{ 119 | Meta: map[string]interface{}{}, 120 | Type: input.TextEvent, 121 | Data: []byte("echo test"), 122 | }: 123 | case <-time.After(time.Second): 124 | t.Fatal("timed out sending event") 125 | } 126 | 127 | // recv output 128 | select { 129 | case ev := <-io.send: 130 | if string(ev.Data) != "test" { 131 | t.Fatal("expected 'test', got: ", string(ev.Data)) 132 | } 133 | case <-time.After(time.Second): 134 | t.Fatal("timed out receiving event") 135 | } 136 | 137 | if err := bot.stop(); err != nil { 138 | t.Fatal(err) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /internal/template/html.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | var ( 4 | HTMLWEB = ` 5 | 6 | 7 | {{title .Alias}} {{title .Type}} 8 | 9 | 10 | 11 | 12 | 13 | 22 |
23 |
24 |
25 |

{{title .Alias}} {{title .Type}}

26 |
27 |
28 | 29 | 30 |
31 | 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 70 | 71 | 72 | ` 73 | ) 74 | -------------------------------------------------------------------------------- /internal/template/main.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | var ( 4 | MainFNC = `package main 5 | 6 | import ( 7 | "github.com/micro/go-log" 8 | "github.com/micro/go-micro" 9 | "{{.Dir}}/handler" 10 | "{{.Dir}}/subscriber" 11 | ) 12 | 13 | func main() { 14 | // New Service 15 | function := micro.NewFunction( 16 | micro.Name("{{.FQDN}}"), 17 | micro.Version("latest"), 18 | ) 19 | 20 | // Initialise function 21 | function.Init() 22 | 23 | // Register Handler 24 | function.Handle(new(handler.Example)) 25 | 26 | // Register Struct as Subscriber 27 | function.Subscribe("{{.FQDN}}", new(subscriber.Example)) 28 | 29 | // Run service 30 | if err := function.Run(); err != nil { 31 | log.Fatal(err) 32 | } 33 | } 34 | ` 35 | 36 | MainSRV = `package main 37 | 38 | import ( 39 | "github.com/micro/go-log" 40 | "github.com/micro/go-micro" 41 | "{{.Dir}}/handler" 42 | "{{.Dir}}/subscriber" 43 | 44 | example "{{.Dir}}/proto/example" 45 | ) 46 | 47 | func main() { 48 | // New Service 49 | service := micro.NewService( 50 | micro.Name("{{.FQDN}}"), 51 | micro.Version("latest"), 52 | ) 53 | 54 | // Initialise service 55 | service.Init() 56 | 57 | // Register Handler 58 | example.RegisterExampleHandler(service.Server(), new(handler.Example)) 59 | 60 | // Register Struct as Subscriber 61 | micro.RegisterSubscriber("{{.FQDN}}", service.Server(), new(subscriber.Example)) 62 | 63 | // Register Function as Subscriber 64 | micro.RegisterSubscriber("{{.FQDN}}", service.Server(), subscriber.Handler) 65 | 66 | // Run service 67 | if err := service.Run(); err != nil { 68 | log.Fatal(err) 69 | } 70 | } 71 | ` 72 | MainAPI = `package main 73 | 74 | import ( 75 | "github.com/micro/go-log" 76 | 77 | "github.com/micro/go-micro" 78 | "{{.Dir}}/handler" 79 | "{{.Dir}}/client" 80 | 81 | example "{{.Dir}}/proto/example" 82 | ) 83 | 84 | func main() { 85 | // New Service 86 | service := micro.NewService( 87 | micro.Name("{{.FQDN}}"), 88 | micro.Version("latest"), 89 | ) 90 | 91 | // Initialise service 92 | service.Init( 93 | // create wrap for the Example srv client 94 | micro.WrapHandler(client.ExampleWrapper(service)), 95 | ) 96 | 97 | // Register Handler 98 | example.RegisterExampleHandler(service.Server(), new(handler.Example)) 99 | 100 | // Run service 101 | if err := service.Run(); err != nil { 102 | log.Fatal(err) 103 | } 104 | } 105 | ` 106 | MainWEB = `package main 107 | 108 | import ( 109 | "github.com/micro/go-log" 110 | "net/http" 111 | 112 | "github.com/micro/go-web" 113 | "{{.Dir}}/handler" 114 | ) 115 | 116 | func main() { 117 | // create new web service 118 | service := web.NewService( 119 | web.Name("{{.FQDN}}"), 120 | web.Version("latest"), 121 | ) 122 | 123 | // initialise service 124 | if err := service.Init(); err != nil { 125 | log.Fatal(err) 126 | } 127 | 128 | // register html handler 129 | service.Handle("/", http.FileServer(http.Dir("html"))) 130 | 131 | // register call handler 132 | service.HandleFunc("/example/call", handler.ExampleCall) 133 | 134 | // run service 135 | if err := service.Run(); err != nil { 136 | log.Fatal(err) 137 | } 138 | } 139 | ` 140 | ) 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Micro [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![GoDoc](https://godoc.org/github.com/micro/micro?status.svg)](https://godoc.org/github.com/micro/micro) [![Travis CI](https://travis-ci.org/micro/micro.svg?branch=master)](https://travis-ci.org/micro/micro) [![Go Report Card](https://goreportcard.com/badge/micro/micro)](https://goreportcard.com/report/github.com/micro/micro) 2 | 3 | Micro is a toolkit for microservice development. 4 | 5 | # Overview 6 | 7 | Micro addresses the key requirements for building scalable systems. It takes the microservice architecture pattern and transforms it into 8 | a set of tools which act as the building blocks of a platform. Micro deals with the complexity of distributed systems and provides 9 | simple abstractions already understood by developers. 10 | 11 | 12 | 13 | Technology is always evolving. The infrastructure stack is constantly changing. Micro is a pluggable toolkit which addresses these issues. 14 | Plug in any stack or underlying technology. Build future-proof systems using micro. 15 | 16 | ## Features 17 | 18 | The toolkit is composed of the following features: 19 | 20 | - **API Gateway:** A single entry point with dynamic request routing using service discovery. The API gateway allows you to build a scalable 21 | microservice architecture on the backend and consolidate serving a public api on the frontend. The micro api provides powerful routing 22 | via discovery and pluggable handlers to serve http, grpc, websockets, publish events and more. 23 | 24 | - **Interactive CLI:** A CLI to describe, query and interact directly with your platform and services from the terminal. The CLI 25 | gives you all the commands you expect to understand what's happening with your micro services. It also includes an interactive mode. 26 | 27 | - **Service Proxy:** A transparent proxy built on [Go Micro](https://github.com/micro/go-micro) and the [MUCP](https://github.com/micro/protocol) 28 | protocol. Offload service discovery, load balancing, fault tolerance, message encoding, middleware, monitoring and more to a single a location. 29 | Run it standalone or alongside your service. 30 | 31 | - **Service Templates:** Generate new service templates to get started quickly. Micro provides predefined templates for writing micro services. 32 | Always start in the same way, build identical services to be more productive. 33 | 34 | - **SlackOps Bot:** A bot which runs on your platform and lets you manage your applications from Slack itself. The micro bot enables ChatOps 35 | and gives you the ability to do everything with your team via messaging. It also includes ability to create slack commmands as services which 36 | are discovered dynamically. 37 | 38 | - **Web Dashboard:** The web dashboard allows you to explore your services, describe their endpoints, the request and response formats and even 39 | query them directly. The dashboard also includes a built in CLI like experience for developers who want to drop into the terminal on the fly. 40 | 41 | ## Getting Started 42 | 43 | See the [docs](https://micro.mu/docs/toolkit.html) for detailed information on the architecture, installation and use of the toolkit. 44 | 45 | ## Sponsors 46 | 47 | Sixt is an Enterprise Sponsor of Micro 48 | 49 | 50 | -------------------------------------------------------------------------------- /bot/proto/bot.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: github.com/micro/micro/bot/proto/bot.proto 3 | 4 | /* 5 | Package go_micro_bot is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | github.com/micro/micro/bot/proto/bot.proto 9 | 10 | It has these top-level messages: 11 | HelpRequest 12 | HelpResponse 13 | ExecRequest 14 | ExecResponse 15 | */ 16 | package go_micro_bot 17 | 18 | import proto "github.com/golang/protobuf/proto" 19 | import fmt "fmt" 20 | import math "math" 21 | 22 | import ( 23 | context "context" 24 | client "github.com/micro/go-micro/client" 25 | server "github.com/micro/go-micro/server" 26 | ) 27 | 28 | // Reference imports to suppress errors if they are not otherwise used. 29 | var _ = proto.Marshal 30 | var _ = fmt.Errorf 31 | var _ = math.Inf 32 | 33 | // This is a compile-time assertion to ensure that this generated file 34 | // is compatible with the proto package it is being compiled against. 35 | // A compilation error at this line likely means your copy of the 36 | // proto package needs to be updated. 37 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 38 | 39 | // Reference imports to suppress errors if they are not otherwise used. 40 | var _ context.Context 41 | var _ client.Option 42 | var _ server.Option 43 | 44 | // Client API for Command service 45 | 46 | type CommandService interface { 47 | Help(ctx context.Context, in *HelpRequest, opts ...client.CallOption) (*HelpResponse, error) 48 | Exec(ctx context.Context, in *ExecRequest, opts ...client.CallOption) (*ExecResponse, error) 49 | } 50 | 51 | type commandService struct { 52 | c client.Client 53 | serviceName string 54 | } 55 | 56 | func NewCommandService(serviceName string, c client.Client) CommandService { 57 | if c == nil { 58 | c = client.NewClient() 59 | } 60 | if len(serviceName) == 0 { 61 | serviceName = "go.micro.bot" 62 | } 63 | return &commandService{ 64 | c: c, 65 | serviceName: serviceName, 66 | } 67 | } 68 | 69 | func (c *commandService) Help(ctx context.Context, in *HelpRequest, opts ...client.CallOption) (*HelpResponse, error) { 70 | req := c.c.NewRequest(c.serviceName, "Command.Help", in) 71 | out := new(HelpResponse) 72 | err := c.c.Call(ctx, req, out, opts...) 73 | if err != nil { 74 | return nil, err 75 | } 76 | return out, nil 77 | } 78 | 79 | func (c *commandService) Exec(ctx context.Context, in *ExecRequest, opts ...client.CallOption) (*ExecResponse, error) { 80 | req := c.c.NewRequest(c.serviceName, "Command.Exec", in) 81 | out := new(ExecResponse) 82 | err := c.c.Call(ctx, req, out, opts...) 83 | if err != nil { 84 | return nil, err 85 | } 86 | return out, nil 87 | } 88 | 89 | // Server API for Command service 90 | 91 | type CommandHandler interface { 92 | Help(context.Context, *HelpRequest, *HelpResponse) error 93 | Exec(context.Context, *ExecRequest, *ExecResponse) error 94 | } 95 | 96 | func RegisterCommandHandler(s server.Server, hdlr CommandHandler, opts ...server.HandlerOption) { 97 | type command interface { 98 | Help(ctx context.Context, in *HelpRequest, out *HelpResponse) error 99 | Exec(ctx context.Context, in *ExecRequest, out *ExecResponse) error 100 | } 101 | type Command struct { 102 | command 103 | } 104 | h := &commandHandler{hdlr} 105 | s.Handle(s.NewHandler(&Command{h}, opts...)) 106 | } 107 | 108 | type commandHandler struct { 109 | CommandHandler 110 | } 111 | 112 | func (h *commandHandler) Help(ctx context.Context, in *HelpRequest, out *HelpResponse) error { 113 | return h.CommandHandler.Help(ctx, in, out) 114 | } 115 | 116 | func (h *commandHandler) Exec(ctx context.Context, in *ExecRequest, out *ExecResponse) error { 117 | return h.CommandHandler.Exec(ctx, in, out) 118 | } 119 | -------------------------------------------------------------------------------- /internal/handler/rpc.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | "github.com/micro/go-micro/client" 11 | "github.com/micro/go-micro/cmd" 12 | "github.com/micro/go-micro/errors" 13 | "github.com/micro/micro/internal/helper" 14 | ) 15 | 16 | type rpcRequest struct { 17 | Service string 18 | Endpoint string 19 | Method string 20 | Address string 21 | Request interface{} 22 | } 23 | 24 | // RPC Handler passes on a JSON or form encoded RPC request to 25 | // a service. 26 | func RPC(w http.ResponseWriter, r *http.Request) { 27 | if r.Method != "POST" { 28 | http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 29 | return 30 | } 31 | defer r.Body.Close() 32 | 33 | badRequest := func(description string) { 34 | e := errors.BadRequest("go.micro.rpc", description) 35 | w.WriteHeader(400) 36 | w.Write([]byte(e.Error())) 37 | } 38 | 39 | var service, endpoint, address string 40 | var request interface{} 41 | 42 | // response content type 43 | w.Header().Set("Content-Type", "application/json") 44 | 45 | ct := r.Header.Get("Content-Type") 46 | 47 | // Strip charset from Content-Type (like `application/json; charset=UTF-8`) 48 | if idx := strings.IndexRune(ct, ';'); idx >= 0 { 49 | ct = ct[:idx] 50 | } 51 | 52 | switch ct { 53 | case "application/json": 54 | var rpcReq rpcRequest 55 | 56 | d := json.NewDecoder(r.Body) 57 | d.UseNumber() 58 | 59 | if err := d.Decode(&rpcReq); err != nil { 60 | badRequest(err.Error()) 61 | return 62 | } 63 | 64 | service = rpcReq.Service 65 | endpoint = rpcReq.Endpoint 66 | address = rpcReq.Address 67 | request = rpcReq.Request 68 | if len(endpoint) == 0 { 69 | endpoint = rpcReq.Method 70 | } 71 | 72 | // JSON as string 73 | if req, ok := rpcReq.Request.(string); ok { 74 | d := json.NewDecoder(strings.NewReader(req)) 75 | d.UseNumber() 76 | 77 | if err := d.Decode(&request); err != nil { 78 | badRequest("error decoding request string: " + err.Error()) 79 | return 80 | } 81 | } 82 | default: 83 | r.ParseForm() 84 | service = r.Form.Get("service") 85 | endpoint = r.Form.Get("endpoint") 86 | address = r.Form.Get("address") 87 | if len(endpoint) == 0 { 88 | endpoint = r.Form.Get("method") 89 | } 90 | 91 | d := json.NewDecoder(strings.NewReader(r.Form.Get("request"))) 92 | d.UseNumber() 93 | 94 | if err := d.Decode(&request); err != nil { 95 | badRequest("error decoding request string: " + err.Error()) 96 | return 97 | } 98 | } 99 | 100 | if len(service) == 0 { 101 | badRequest("invalid service") 102 | return 103 | } 104 | 105 | if len(endpoint) == 0 { 106 | badRequest("invalid endpoint") 107 | return 108 | } 109 | 110 | // create request/response 111 | var response json.RawMessage 112 | var err error 113 | req := (*cmd.DefaultOptions().Client).NewRequest(service, endpoint, request, client.WithContentType("application/json")) 114 | 115 | // create context 116 | ctx := helper.RequestToContext(r) 117 | 118 | var opts []client.CallOption 119 | 120 | timeout, _ := strconv.Atoi(r.Header.Get("Timeout")) 121 | // set timeout 122 | if timeout > 0 { 123 | opts = append(opts, client.WithRequestTimeout(time.Duration(timeout)*time.Second)) 124 | } 125 | 126 | // remote call 127 | if len(address) > 0 { 128 | opts = append(opts, client.WithAddress(address)) 129 | } 130 | 131 | // remote call 132 | err = (*cmd.DefaultOptions().Client).Call(ctx, req, &response, opts...) 133 | if err != nil { 134 | ce := errors.Parse(err.Error()) 135 | switch ce.Code { 136 | case 0: 137 | // assuming it's totally screwed 138 | ce.Code = 500 139 | ce.Id = "go.micro.rpc" 140 | ce.Status = http.StatusText(500) 141 | ce.Detail = "error during request: " + ce.Detail 142 | w.WriteHeader(500) 143 | default: 144 | w.WriteHeader(int(ce.Code)) 145 | } 146 | w.Write([]byte(ce.Error())) 147 | return 148 | } 149 | 150 | b, _ := response.MarshalJSON() 151 | w.Header().Set("Content-Length", strconv.Itoa(len(b))) 152 | w.Write(b) 153 | } 154 | -------------------------------------------------------------------------------- /internal/stats/stats.go: -------------------------------------------------------------------------------- 1 | package stats 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "html/template" 7 | "net/http" 8 | "runtime" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type stats struct { 14 | sync.RWMutex 15 | 16 | Started int64 `json:"started"` 17 | Memory string `json:"memory"` 18 | Threads int `json:"threads"` 19 | GC string `json:"gc_pause"` 20 | 21 | Counters []*counter `json:"counters"` 22 | 23 | running bool 24 | exit chan bool 25 | } 26 | 27 | type counter struct { 28 | // time created 29 | Timestamp int64 `json:"timestamp"` 30 | // counters 31 | Status map[string]int `json:"status_codes"` 32 | Total int `json:"total_reqs"` 33 | } 34 | 35 | var ( 36 | // 5 second window 37 | window = time.Second * 5 38 | // 120 seconds total 39 | total = 24 40 | ) 41 | 42 | func render(w http.ResponseWriter, r *http.Request, tmpl string, data interface{}) { 43 | t, err := template.New("template").Funcs(template.FuncMap{ 44 | // "format": format, 45 | }).Parse(layoutTemplate) 46 | if err != nil { 47 | http.Error(w, "Error occurred:"+err.Error(), 500) 48 | return 49 | } 50 | t, err = t.Parse(tmpl) 51 | if err != nil { 52 | http.Error(w, "Error occurred:"+err.Error(), 500) 53 | return 54 | } 55 | if err := t.ExecuteTemplate(w, "layout", data); err != nil { 56 | http.Error(w, "Error occurred:"+err.Error(), 500) 57 | } 58 | } 59 | 60 | func (s *stats) run() { 61 | t := time.NewTicker(window) 62 | w := 0 63 | 64 | for { 65 | select { 66 | case <-s.exit: 67 | t.Stop() 68 | return 69 | case <-t.C: 70 | // roll 71 | s.Lock() 72 | s.Counters = append(s.Counters, &counter{ 73 | Timestamp: time.Now().Unix(), 74 | Status: make(map[string]int), 75 | }) 76 | if len(s.Counters) >= total { 77 | s.Counters = s.Counters[1:] 78 | } 79 | 80 | w++ 81 | if w >= 2 { 82 | var mstat runtime.MemStats 83 | runtime.ReadMemStats(&mstat) 84 | s.Threads = runtime.NumGoroutine() 85 | s.Memory = fmt.Sprintf("%.2fmb", float64(mstat.Alloc)/float64(1024*1024)) 86 | s.GC = fmt.Sprintf("%.3fms", float64(mstat.PauseTotalNs)/(1000*1000)) 87 | w = 0 88 | } 89 | s.Unlock() 90 | } 91 | } 92 | } 93 | 94 | func (s *stats) Record(c string, t int) { 95 | s.Lock() 96 | counter := s.Counters[len(s.Counters)-1] 97 | counter.Status[c] += t 98 | counter.Total += t 99 | s.Counters[len(s.Counters)-1] = counter 100 | s.Unlock() 101 | } 102 | 103 | func (s *stats) ServeHTTP(h http.Handler) http.Handler { 104 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 105 | var code string 106 | rw := &writer{w, 200} 107 | 108 | h.ServeHTTP(w, r) 109 | 110 | switch { 111 | case rw.status >= 500: 112 | code = "50x" 113 | case rw.status >= 400: 114 | code = "40x" 115 | case rw.status >= 300: 116 | code = "30x" 117 | case rw.status >= 200: 118 | code = "20x" 119 | } 120 | 121 | s.Record(code, 1) 122 | }) 123 | } 124 | 125 | func (s *stats) StatsHandler(w http.ResponseWriter, r *http.Request) { 126 | if ct := r.Header.Get("Content-Type"); ct == "application/json" { 127 | s.RLock() 128 | b, err := json.Marshal(s) 129 | s.RUnlock() 130 | if err != nil { 131 | http.Error(w, err.Error(), 500) 132 | return 133 | } 134 | w.Header().Set("Content-Type", ct) 135 | w.Write(b) 136 | return 137 | } 138 | 139 | render(w, r, statsTemplate, nil) 140 | } 141 | 142 | func (s *stats) Start() error { 143 | s.Lock() 144 | defer s.Unlock() 145 | 146 | if s.running { 147 | return nil 148 | } 149 | 150 | s.Started = time.Now().Unix() 151 | s.exit = make(chan bool) 152 | go s.run() 153 | return nil 154 | } 155 | 156 | func (s *stats) Stop() error { 157 | s.Lock() 158 | defer s.Unlock() 159 | 160 | if !s.running { 161 | return nil 162 | } 163 | 164 | close(s.exit) 165 | s.Started = 0 166 | return nil 167 | } 168 | 169 | func New() *stats { 170 | var mstat runtime.MemStats 171 | runtime.ReadMemStats(&mstat) 172 | 173 | return &stats{ 174 | Threads: runtime.NumGoroutine(), 175 | Memory: fmt.Sprintf("%.2fmb", float64(mstat.Alloc)/float64(1024*1024)), 176 | GC: fmt.Sprintf("%.3fms", float64(mstat.PauseTotalNs)/(1000*1000)), 177 | Counters: []*counter{ 178 | &counter{ 179 | Timestamp: time.Now().Unix(), 180 | Status: make(map[string]int), 181 | }, 182 | }, 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /cli/cli.go: -------------------------------------------------------------------------------- 1 | // Package cli is a command line interface 2 | package cli 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/chzyer/readline" 10 | "github.com/micro/cli" 11 | ) 12 | 13 | var ( 14 | prompt = "micro> " 15 | 16 | commands = map[string]*command{ 17 | "quit": &command{"quit", "Exit the CLI", quit}, 18 | "exit": &command{"exit", "Exit the CLI", quit}, 19 | "call": &command{"call", "Call a service", callService}, 20 | "list": &command{"list", "List services", listServices}, 21 | "get": &command{"get", "Get service info", getService}, 22 | "stream": &command{"stream", "Stream a call to a service", streamService}, 23 | "publish": &command{"publish", "Publish a message to a topic", publish}, 24 | "health": &command{"health", "Get service health", queryHealth}, 25 | "stats": &command{"stats", "Get service stats", queryStats}, 26 | "register": &command{"register", "Register a service", registerService}, 27 | "deregister": &command{"deregister", "Deregister a service", deregisterService}, 28 | } 29 | ) 30 | 31 | type command struct { 32 | name string 33 | usage string 34 | exec exec 35 | } 36 | 37 | func runc(c *cli.Context) { 38 | commands["help"] = &command{"help", "CLI usage", help} 39 | alias := map[string]string{ 40 | "?": "help", 41 | "ls": "list", 42 | } 43 | 44 | r, err := readline.New(prompt) 45 | if err != nil { 46 | fmt.Fprint(os.Stdout, err) 47 | os.Exit(1) 48 | } 49 | defer r.Close() 50 | 51 | for { 52 | args, err := r.Readline() 53 | if err != nil { 54 | fmt.Fprint(os.Stdout, err) 55 | return 56 | } 57 | 58 | args = strings.TrimSpace(args) 59 | 60 | // skip no args 61 | if len(args) == 0 { 62 | continue 63 | } 64 | 65 | parts := strings.Split(args, " ") 66 | if len(parts) == 0 { 67 | continue 68 | } 69 | 70 | name := parts[0] 71 | 72 | // get alias 73 | if n, ok := alias[name]; ok { 74 | name = n 75 | } 76 | 77 | if cmd, ok := commands[name]; ok { 78 | rsp, err := cmd.exec(c, parts[1:]) 79 | if err != nil { 80 | println(err.Error()) 81 | continue 82 | } 83 | println(string(rsp)) 84 | } else { 85 | println("unknown command") 86 | } 87 | } 88 | } 89 | 90 | func registryCommands() []cli.Command { 91 | return []cli.Command{ 92 | { 93 | Name: "list", 94 | Usage: "List items in registry", 95 | Subcommands: []cli.Command{ 96 | { 97 | Name: "services", 98 | Usage: "List services in registry", 99 | Action: printer(listServices), 100 | }, 101 | }, 102 | }, 103 | { 104 | Name: "register", 105 | Usage: "Register an item in the registry", 106 | Subcommands: []cli.Command{ 107 | { 108 | Name: "service", 109 | Usage: "Register a service with JSON definition", 110 | Action: printer(registerService), 111 | }, 112 | }, 113 | }, 114 | { 115 | Name: "deregister", 116 | Usage: "Deregister an item in the registry", 117 | Subcommands: []cli.Command{ 118 | { 119 | Name: "service", 120 | Usage: "Deregister a service with JSON definition", 121 | Action: printer(deregisterService), 122 | }, 123 | }, 124 | }, 125 | { 126 | Name: "get", 127 | Usage: "Get item from registry", 128 | Subcommands: []cli.Command{ 129 | { 130 | Name: "service", 131 | Usage: "Get service from registry", 132 | Action: printer(getService), 133 | }, 134 | }, 135 | }, 136 | } 137 | } 138 | 139 | func Commands() []cli.Command { 140 | commands := []cli.Command{ 141 | { 142 | Name: "cli", 143 | Usage: "Run the interactive CLI", 144 | Action: runc, 145 | }, 146 | { 147 | Name: "registry", 148 | Usage: "Query registry", 149 | Subcommands: registryCommands(), 150 | }, 151 | { 152 | Name: "call", 153 | Usage: "Call a service", 154 | Action: printer(callService), 155 | }, 156 | { 157 | Name: "stream", 158 | Usage: "Create a service stream", 159 | Action: printer(streamService), 160 | }, 161 | { 162 | Name: "publish", 163 | Usage: "Publish a message to a topic", 164 | Action: printer(publish), 165 | }, 166 | { 167 | Name: "health", 168 | Usage: "Query the health of a service", 169 | Action: printer(queryHealth), 170 | }, 171 | { 172 | Name: "stats", 173 | Usage: "Query the stats of a service", 174 | Action: printer(queryStats), 175 | }, 176 | } 177 | 178 | return append(commands, registryCommands()...) 179 | } 180 | -------------------------------------------------------------------------------- /proxy/proxy.go: -------------------------------------------------------------------------------- 1 | // Package proxy is a cli proxy 2 | package proxy 3 | 4 | import ( 5 | "fmt" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gorilla/mux" 10 | "github.com/micro/cli" 11 | "github.com/micro/go-api/server" 12 | httpapi "github.com/micro/go-api/server/http" 13 | "github.com/micro/go-log" 14 | "github.com/micro/go-micro" 15 | "github.com/micro/go-proxy/router/mucp" 16 | "github.com/micro/micro/internal/handler" 17 | "github.com/micro/micro/internal/helper" 18 | "github.com/micro/micro/internal/stats" 19 | "github.com/micro/micro/plugin" 20 | 21 | ahandler "github.com/micro/go-api/handler" 22 | abroker "github.com/micro/go-api/handler/broker" 23 | aregistry "github.com/micro/go-api/handler/registry" 24 | ) 25 | 26 | type srv struct { 27 | *mux.Router 28 | } 29 | 30 | var ( 31 | // Name of the proxy 32 | Name = "go.micro.proxy" 33 | // The http address of the proxy 34 | Address = ":8081" 35 | // The backend host to route to 36 | Backend string 37 | // The paths for http endpoints 38 | BrokerPath = "/broker" 39 | RegistryPath = "/registry" 40 | RPCPath = "/rpc" 41 | ) 42 | 43 | func run(ctx *cli.Context, srvOpts ...micro.Option) { 44 | if len(ctx.GlobalString("server_name")) > 0 { 45 | Name = ctx.GlobalString("server_name") 46 | } 47 | if len(ctx.String("address")) > 0 { 48 | Address = ctx.String("address") 49 | } 50 | if len(ctx.String("backend")) > 0 { 51 | Backend = ctx.String("backend") 52 | } 53 | 54 | // Init plugins 55 | for _, p := range Plugins() { 56 | p.Init(ctx) 57 | } 58 | 59 | var opts []server.Option 60 | 61 | if ctx.GlobalBool("enable_acme") { 62 | hosts := helper.ACMEHosts(ctx) 63 | opts = append(opts, server.EnableACME(true)) 64 | opts = append(opts, server.ACMEHosts(hosts...)) 65 | } else if ctx.GlobalBool("enable_tls") { 66 | config, err := helper.TLSConfig(ctx) 67 | if err != nil { 68 | fmt.Println(err.Error()) 69 | return 70 | } 71 | 72 | opts = append(opts, server.EnableTLS(true)) 73 | opts = append(opts, server.TLSConfig(config)) 74 | } 75 | 76 | r := mux.NewRouter() 77 | var h http.Handler = r 78 | 79 | if ctx.GlobalBool("enable_stats") { 80 | st := stats.New() 81 | r.Handle("/stats", http.HandlerFunc(st.StatsHandler)) 82 | h = st.ServeHTTP(r) 83 | st.Start() 84 | defer st.Stop() 85 | } 86 | 87 | // new server 88 | srv := httpapi.NewServer(Address) 89 | srv.Init(opts...) 90 | 91 | // service opts 92 | srvOpts = append(srvOpts, micro.Name(Name)) 93 | if i := time.Duration(ctx.GlobalInt("register_ttl")); i > 0 { 94 | srvOpts = append(srvOpts, micro.RegisterTTL(i*time.Second)) 95 | } 96 | if i := time.Duration(ctx.GlobalInt("register_interval")); i > 0 { 97 | srvOpts = append(srvOpts, micro.RegisterInterval(i*time.Second)) 98 | } 99 | 100 | // set backend 101 | if len(Backend) > 0 { 102 | srvOpts = append(srvOpts, mucp.WithBackend(Backend)) 103 | } 104 | 105 | // Initialise Server 106 | service := mucp.NewService(srvOpts...) 107 | 108 | log.Logf("Registering Registry handler at %s", RegistryPath) 109 | r.Handle(RegistryPath, aregistry.NewHandler(ahandler.WithService(service))) 110 | 111 | log.Logf("Registering RPC handler at %s", RPCPath) 112 | r.Handle(RPCPath, http.HandlerFunc(handler.RPC)) 113 | 114 | log.Logf("Registering Broker handler at %s", BrokerPath) 115 | br := abroker.NewHandler( 116 | ahandler.WithService(service), 117 | ) 118 | r.Handle(BrokerPath, br) 119 | 120 | // reverse wrap handler 121 | plugins := append(Plugins(), plugin.Plugins()...) 122 | for i := len(plugins); i > 0; i-- { 123 | h = plugins[i-1].Handler()(h) 124 | } 125 | 126 | srv.Handle("/", h) 127 | 128 | if err := srv.Start(); err != nil { 129 | log.Fatal(err) 130 | } 131 | 132 | // Run server 133 | if err := service.Run(); err != nil { 134 | log.Fatal(err) 135 | } 136 | 137 | if err := srv.Stop(); err != nil { 138 | log.Fatal(err) 139 | } 140 | } 141 | 142 | func Commands(options ...micro.Option) []cli.Command { 143 | command := cli.Command{ 144 | Name: "proxy", 145 | Usage: "Run the service proxy", 146 | Flags: []cli.Flag{ 147 | cli.StringFlag{ 148 | Name: "address", 149 | Usage: "Set the proxy http address e.g 0.0.0.0:8081", 150 | EnvVar: "MICRO_PROXY_ADDRESS", 151 | }, 152 | cli.StringFlag{ 153 | Name: "backend", 154 | Usage: "Set the backend to route to e.g greeter or localhost:9090", 155 | EnvVar: "MICRO_PROXY_BACKEND", 156 | }, 157 | }, 158 | Action: func(ctx *cli.Context) { 159 | run(ctx, options...) 160 | }, 161 | } 162 | 163 | for _, p := range Plugins() { 164 | if cmds := p.Commands(); len(cmds) > 0 { 165 | command.Subcommands = append(command.Subcommands, cmds...) 166 | } 167 | 168 | if flags := p.Flags(); len(flags) > 0 { 169 | command.Flags = append(command.Flags, flags...) 170 | } 171 | } 172 | 173 | return []cli.Command{command} 174 | } 175 | -------------------------------------------------------------------------------- /cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | ccli "github.com/micro/cli" 5 | "github.com/micro/go-micro" 6 | "github.com/micro/go-micro/cmd" 7 | "github.com/micro/micro/api" 8 | "github.com/micro/micro/bot" 9 | "github.com/micro/micro/cli" 10 | "github.com/micro/micro/new" 11 | "github.com/micro/micro/plugin" 12 | "github.com/micro/micro/proxy" 13 | "github.com/micro/micro/service" 14 | "github.com/micro/micro/web" 15 | 16 | // include usage 17 | _ "github.com/micro/micro/internal/usage" 18 | ) 19 | 20 | var ( 21 | name = "micro" 22 | description = "A microservice toolkit" 23 | version = "1.0.0" 24 | ) 25 | 26 | func setup(app *ccli.App) { 27 | app.Flags = append(app.Flags, 28 | ccli.BoolFlag{ 29 | Name: "enable_acme", 30 | Usage: "Enables ACME support via Let's Encrypt. ACME hosts should also be specified.", 31 | EnvVar: "MICRO_ENABLE_ACME", 32 | }, 33 | ccli.StringFlag{ 34 | Name: "acme_hosts", 35 | Usage: "Comma separated list of hostnames to manage ACME certs for", 36 | EnvVar: "MICRO_ACME_HOSTS", 37 | }, 38 | ccli.BoolFlag{ 39 | Name: "enable_tls", 40 | Usage: "Enable TLS support. Expects cert and key file to be specified", 41 | EnvVar: "MICRO_ENABLE_TLS", 42 | }, 43 | ccli.StringFlag{ 44 | Name: "tls_cert_file", 45 | Usage: "Path to the TLS Certificate file", 46 | EnvVar: "MICRO_TLS_CERT_FILE", 47 | }, 48 | ccli.StringFlag{ 49 | Name: "tls_key_file", 50 | Usage: "Path to the TLS Key file", 51 | EnvVar: "MICRO_TLS_KEY_FILE", 52 | }, 53 | ccli.StringFlag{ 54 | Name: "tls_client_ca_file", 55 | Usage: "Path to the TLS CA file to verify clients against", 56 | EnvVar: "MICRO_TLS_CLIENT_CA_FILE", 57 | }, 58 | ccli.StringFlag{ 59 | Name: "api_address", 60 | Usage: "Set the api address e.g 0.0.0.0:8080", 61 | EnvVar: "MICRO_API_ADDRESS", 62 | }, 63 | ccli.StringFlag{ 64 | Name: "proxy_address", 65 | Usage: "Proxy requests via the HTTP address specified", 66 | EnvVar: "MICRO_PROXY_ADDRESS", 67 | }, 68 | ccli.StringFlag{ 69 | Name: "web_address", 70 | Usage: "Set the web UI address e.g 0.0.0.0:8082", 71 | EnvVar: "MICRO_WEB_ADDRESS", 72 | }, 73 | ccli.StringFlag{ 74 | Name: "api_handler", 75 | Usage: "Specify the request handler to be used for mapping HTTP requests to services; {api, proxy, rpc}", 76 | EnvVar: "MICRO_API_HANDLER", 77 | }, 78 | ccli.StringFlag{ 79 | Name: "api_namespace", 80 | Usage: "Set the namespace used by the API e.g. com.example.api", 81 | EnvVar: "MICRO_API_NAMESPACE", 82 | }, 83 | ccli.StringFlag{ 84 | Name: "web_namespace", 85 | Usage: "Set the namespace used by the Web proxy e.g. com.example.web", 86 | EnvVar: "MICRO_WEB_NAMESPACE", 87 | }, 88 | ccli.BoolFlag{ 89 | Name: "enable_stats", 90 | Usage: "Enable stats", 91 | EnvVar: "MICRO_ENABLE_STATS", 92 | }, 93 | ccli.BoolTFlag{ 94 | Name: "report_usage", 95 | Usage: "Report usage statistics", 96 | EnvVar: "MICRO_REPORT_USAGE", 97 | }, 98 | ) 99 | 100 | plugins := plugin.Plugins() 101 | 102 | for _, p := range plugins { 103 | if flags := p.Flags(); len(flags) > 0 { 104 | app.Flags = append(app.Flags, flags...) 105 | } 106 | 107 | if cmds := p.Commands(); len(cmds) > 0 { 108 | app.Commands = append(app.Commands, cmds...) 109 | } 110 | } 111 | 112 | before := app.Before 113 | 114 | app.Before = func(ctx *ccli.Context) error { 115 | if len(ctx.String("api_handler")) > 0 { 116 | api.Handler = ctx.String("api_handler") 117 | } 118 | if len(ctx.String("api_address")) > 0 { 119 | api.Address = ctx.String("api_address") 120 | } 121 | if len(ctx.String("proxy_address")) > 0 { 122 | proxy.Address = ctx.String("proxy_address") 123 | } 124 | if len(ctx.String("web_address")) > 0 { 125 | web.Address = ctx.String("web_address") 126 | } 127 | if len(ctx.String("api_namespace")) > 0 { 128 | api.Namespace = ctx.String("api_namespace") 129 | } 130 | if len(ctx.String("web_namespace")) > 0 { 131 | web.Namespace = ctx.String("web_namespace") 132 | } 133 | 134 | for _, p := range plugins { 135 | if err := p.Init(ctx); err != nil { 136 | return err 137 | } 138 | } 139 | 140 | // now do previous before 141 | return before(ctx) 142 | } 143 | } 144 | 145 | // Init initialised the command line 146 | func Init(options ...micro.Option) { 147 | Setup(cmd.App(), options...) 148 | 149 | cmd.Init( 150 | cmd.Name(name), 151 | cmd.Description(description), 152 | cmd.Version(version), 153 | ) 154 | } 155 | 156 | // Setup sets up a cli.App 157 | func Setup(app *ccli.App, options ...micro.Option) { 158 | app.Commands = append(app.Commands, api.Commands(options...)...) 159 | app.Commands = append(app.Commands, bot.Commands()...) 160 | app.Commands = append(app.Commands, cli.Commands()...) 161 | app.Commands = append(app.Commands, proxy.Commands(options...)...) 162 | app.Commands = append(app.Commands, service.Commands(options...)...) 163 | app.Commands = append(app.Commands, new.Commands()...) 164 | app.Commands = append(app.Commands, web.Commands(options...)...) 165 | app.Action = func(context *ccli.Context) { ccli.ShowAppHelp(context) } 166 | 167 | setup(app) 168 | } 169 | -------------------------------------------------------------------------------- /internal/template/handler.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | var ( 4 | HandlerFNC = `package handler 5 | 6 | import ( 7 | "context" 8 | 9 | example "{{.Dir}}/proto/example" 10 | ) 11 | 12 | type Example struct{} 13 | 14 | // Call is a single request handler called via client.Call or the generated client code 15 | func (e *Example) Call(ctx context.Context, req *example.Request, rsp *example.Response) error { 16 | rsp.Msg = "Hello " + req.Name 17 | return nil 18 | } 19 | ` 20 | 21 | HandlerSRV = `package handler 22 | 23 | import ( 24 | "context" 25 | 26 | "github.com/micro/go-log" 27 | 28 | example "{{.Dir}}/proto/example" 29 | ) 30 | 31 | type Example struct{} 32 | 33 | // Call is a single request handler called via client.Call or the generated client code 34 | func (e *Example) Call(ctx context.Context, req *example.Request, rsp *example.Response) error { 35 | log.Log("Received Example.Call request") 36 | rsp.Msg = "Hello " + req.Name 37 | return nil 38 | } 39 | 40 | // Stream is a server side stream handler called via client.Stream or the generated client code 41 | func (e *Example) Stream(ctx context.Context, req *example.StreamingRequest, stream example.Example_StreamStream) error { 42 | log.Logf("Received Example.Stream request with count: %d", req.Count) 43 | 44 | for i := 0; i < int(req.Count); i++ { 45 | log.Logf("Responding: %d", i) 46 | if err := stream.Send(&example.StreamingResponse{ 47 | Count: int64(i), 48 | }); err != nil { 49 | return err 50 | } 51 | } 52 | 53 | return nil 54 | } 55 | 56 | // PingPong is a bidirectional stream handler called via client.Stream or the generated client code 57 | func (e *Example) PingPong(ctx context.Context, stream example.Example_PingPongStream) error { 58 | for { 59 | req, err := stream.Recv() 60 | if err != nil { 61 | return err 62 | } 63 | log.Logf("Got ping %v", req.Stroke) 64 | if err := stream.Send(&example.Pong{Stroke: req.Stroke}); err != nil { 65 | return err 66 | } 67 | } 68 | } 69 | ` 70 | 71 | SubscriberFNC = `package subscriber 72 | 73 | import ( 74 | "context" 75 | 76 | "github.com/micro/go-log" 77 | 78 | example "{{.Dir}}/proto/example" 79 | ) 80 | 81 | type Example struct{} 82 | 83 | func (e *Example) Handle(ctx context.Context, msg *example.Message) error { 84 | log.Log("Handler Received message: ", msg.Say) 85 | return nil 86 | } 87 | ` 88 | 89 | SubscriberSRV = `package subscriber 90 | 91 | import ( 92 | "context" 93 | "github.com/micro/go-log" 94 | 95 | example "{{.Dir}}/proto/example" 96 | ) 97 | 98 | type Example struct{} 99 | 100 | func (e *Example) Handle(ctx context.Context, msg *example.Message) error { 101 | log.Log("Handler Received message: ", msg.Say) 102 | return nil 103 | } 104 | 105 | func Handler(ctx context.Context, msg *example.Message) error { 106 | log.Log("Function Received message: ", msg.Say) 107 | return nil 108 | } 109 | ` 110 | 111 | HandlerAPI = `package handler 112 | 113 | import ( 114 | "context" 115 | "encoding/json" 116 | "github.com/micro/go-log" 117 | 118 | "{{.Dir}}/client" 119 | "github.com/micro/go-micro/errors" 120 | api "github.com/micro/go-api/proto" 121 | example "github.com/micro/examples/template/srv/proto/example" 122 | ) 123 | 124 | type Example struct{} 125 | 126 | func extractValue(pair *api.Pair) string { 127 | if pair == nil { 128 | return "" 129 | } 130 | if len(pair.Values) == 0 { 131 | return "" 132 | } 133 | return pair.Values[0] 134 | } 135 | 136 | // Example.Call is called by the API as /{{.Alias}}/example/call with post body {"name": "foo"} 137 | func (e *Example) Call(ctx context.Context, req *api.Request, rsp *api.Response) error { 138 | log.Log("Received Example.Call request") 139 | 140 | // extract the client from the context 141 | exampleClient, ok := client.ExampleFromContext(ctx) 142 | if !ok { 143 | return errors.InternalServerError("{{.FQDN}}.example.call", "example client not found") 144 | } 145 | 146 | // make request 147 | response, err := exampleClient.Call(ctx, &example.Request{ 148 | Name: extractValue(req.Post["name"]), 149 | }) 150 | if err != nil { 151 | return errors.InternalServerError("{{.FQDN}}.example.call", err.Error()) 152 | } 153 | 154 | b, _ := json.Marshal(response) 155 | 156 | rsp.StatusCode = 200 157 | rsp.Body = string(b) 158 | 159 | return nil 160 | } 161 | ` 162 | 163 | HandlerWEB = `package handler 164 | 165 | import ( 166 | "context" 167 | "encoding/json" 168 | "net/http" 169 | "time" 170 | 171 | "github.com/micro/go-micro/client" 172 | example "github.com/micro/examples/template/srv/proto/example" 173 | ) 174 | 175 | func ExampleCall(w http.ResponseWriter, r *http.Request) { 176 | // decode the incoming request as json 177 | var request map[string]interface{} 178 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { 179 | http.Error(w, err.Error(), 500) 180 | return 181 | } 182 | 183 | // call the backend service 184 | exampleClient := example.NewExampleService("go.micro.srv.template", client.DefaultClient) 185 | rsp, err := exampleClient.Call(context.TODO(), &example.Request{ 186 | Name: request["name"].(string), 187 | }) 188 | if err != nil { 189 | http.Error(w, err.Error(), 500) 190 | return 191 | } 192 | 193 | // we want to augment the response 194 | response := map[string]interface{}{ 195 | "msg": rsp.Msg, 196 | "ref": time.Now().UnixNano(), 197 | } 198 | 199 | // encode and write the response as json 200 | if err := json.NewEncoder(w).Encode(response); err != nil { 201 | http.Error(w, err.Error(), 500) 202 | return 203 | } 204 | } 205 | ` 206 | ) 207 | -------------------------------------------------------------------------------- /internal/command/bot/commands.go: -------------------------------------------------------------------------------- 1 | package bot 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | 7 | "github.com/micro/cli" 8 | "github.com/micro/go-bot/command" 9 | clic "github.com/micro/micro/internal/command/cli" 10 | ) 11 | 12 | // Echo returns the same message 13 | func Echo(ctx *cli.Context) command.Command { 14 | usage := "echo [text]" 15 | desc := "Returns the [text]" 16 | 17 | return command.NewCommand("echo", usage, desc, func(args ...string) ([]byte, error) { 18 | if len(args) < 2 { 19 | return []byte("echo what?"), nil 20 | } 21 | return []byte(strings.Join(args[1:], " ")), nil 22 | }) 23 | } 24 | 25 | // Hello returns a greeting 26 | func Hello(ctx *cli.Context) command.Command { 27 | usage := "hello" 28 | desc := "Returns a greeting" 29 | 30 | return command.NewCommand("hello", usage, desc, func(args ...string) ([]byte, error) { 31 | return []byte("hey what's up?"), nil 32 | }) 33 | } 34 | 35 | // Ping returns pong 36 | func Ping(ctx *cli.Context) command.Command { 37 | usage := "ping" 38 | desc := "Returns pong" 39 | 40 | return command.NewCommand("ping", usage, desc, func(args ...string) ([]byte, error) { 41 | return []byte("pong"), nil 42 | }) 43 | } 44 | 45 | // Get service returns a service 46 | func Get(ctx *cli.Context) command.Command { 47 | usage := "get service [name]" 48 | desc := "Returns a registered service" 49 | 50 | return command.NewCommand("get", usage, desc, func(args ...string) ([]byte, error) { 51 | if len(args) < 2 { 52 | return []byte("get what?"), nil 53 | } 54 | switch args[1] { 55 | case "service": 56 | if len(args) < 3 { 57 | return []byte("require service name"), nil 58 | } 59 | rsp, err := clic.GetService(ctx, args[2:]) 60 | if err != nil { 61 | return nil, err 62 | } 63 | return rsp, nil 64 | default: 65 | return []byte("unknown command...\nsupported commands: \nget service [name]"), nil 66 | } 67 | }) 68 | } 69 | 70 | // Health returns the health of a service 71 | func Health(ctx *cli.Context) command.Command { 72 | usage := "health [service]" 73 | desc := "Returns health of a service" 74 | 75 | return command.NewCommand("health", usage, desc, func(args ...string) ([]byte, error) { 76 | if len(args) < 2 { 77 | return []byte("health of what?"), nil 78 | } 79 | rsp, err := clic.QueryHealth(ctx, args[1:]) 80 | if err != nil { 81 | return nil, err 82 | } 83 | return rsp, nil 84 | }) 85 | } 86 | 87 | // List returns a list of services 88 | func List(ctx *cli.Context) command.Command { 89 | usage := "list services" 90 | desc := "Returns a list of registered services" 91 | 92 | return command.NewCommand("list", usage, desc, func(args ...string) ([]byte, error) { 93 | if len(args) < 2 { 94 | return []byte("list what?"), nil 95 | } 96 | switch args[1] { 97 | case "services": 98 | rsp, err := clic.ListServices(ctx) 99 | if err != nil { 100 | return nil, err 101 | } 102 | return rsp, nil 103 | default: 104 | return []byte("unknown command...\nsupported commands: \nlist services"), nil 105 | } 106 | }) 107 | } 108 | 109 | // Call returns a service call 110 | func Call(ctx *cli.Context) command.Command { 111 | usage := "call [service] [endpoint] [request]" 112 | desc := "Returns the response for a service call" 113 | 114 | return command.NewCommand("call", usage, desc, func(args ...string) ([]byte, error) { 115 | var cargs []string 116 | 117 | for _, arg := range args { 118 | if len(strings.TrimSpace(arg)) == 0 { 119 | continue 120 | } 121 | cargs = append(cargs, arg) 122 | } 123 | 124 | if len(cargs) < 2 { 125 | return []byte("call what?"), nil 126 | } 127 | 128 | rsp, err := clic.CallService(ctx, cargs[1:]) 129 | if err != nil { 130 | return nil, err 131 | } 132 | return rsp, nil 133 | }) 134 | } 135 | 136 | // Register registers a service 137 | func Register(ctx *cli.Context) command.Command { 138 | usage := "register service [definition]" 139 | desc := "Registers a service" 140 | 141 | return command.NewCommand("register", usage, desc, func(args ...string) ([]byte, error) { 142 | if len(args) < 2 { 143 | return []byte("register what?"), nil 144 | } 145 | switch args[1] { 146 | case "service": 147 | if len(args) < 3 { 148 | return []byte("require service definition"), nil 149 | } 150 | rsp, err := clic.RegisterService(ctx, args[2:]) 151 | if err != nil { 152 | return nil, err 153 | } 154 | return rsp, nil 155 | default: 156 | return []byte("unknown command...\nsupported commands: \nregister service [definition]"), nil 157 | } 158 | }) 159 | } 160 | 161 | // Deregister registers a service 162 | func Deregister(ctx *cli.Context) command.Command { 163 | usage := "deregister service [definition]" 164 | desc := "Deregisters a service" 165 | 166 | return command.NewCommand("deregister", usage, desc, func(args ...string) ([]byte, error) { 167 | if len(args) < 2 { 168 | return []byte("deregister what?"), nil 169 | } 170 | switch args[1] { 171 | case "service": 172 | if len(args) < 3 { 173 | return []byte("require service definition"), nil 174 | } 175 | rsp, err := clic.DeregisterService(ctx, args[2:]) 176 | if err != nil { 177 | return nil, err 178 | } 179 | return rsp, nil 180 | default: 181 | return []byte("unknown command...\nsupported commands: \nderegister service [definition]"), nil 182 | } 183 | }) 184 | } 185 | 186 | // Laws of robotics 187 | func ThreeLaws(ctx *cli.Context) command.Command { 188 | usage := "the three laws" 189 | desc := "Returns the three laws of robotics" 190 | 191 | return command.NewCommand("the three laws", usage, desc, func(args ...string) ([]byte, error) { 192 | laws := []string{ 193 | "1. A robot may not injure a human being or, through inaction, allow a human being to come to harm.", 194 | "2. A robot must obey the orders given it by human beings except where such orders would conflict with the First Law.", 195 | "3. A robot must protect its own existence as long as such protection does not conflict with the First or Second Laws.", 196 | } 197 | return []byte("\n" + strings.Join(laws, "\n")), nil 198 | }) 199 | } 200 | 201 | // Time returns the time 202 | func Time(ctx *cli.Context) command.Command { 203 | usage := "time" 204 | desc := "Returns the server time" 205 | 206 | return command.NewCommand("time", usage, desc, func(args ...string) ([]byte, error) { 207 | t := time.Now().Format(time.RFC1123) 208 | return []byte("Server time is: " + t), nil 209 | }) 210 | } 211 | -------------------------------------------------------------------------------- /internal/usage/proto/usage.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: github.com/micro/micro/internal/usage/proto/usage.proto 3 | 4 | package usage 5 | 6 | import proto "github.com/golang/protobuf/proto" 7 | import fmt "fmt" 8 | import math "math" 9 | 10 | // Reference imports to suppress errors if they are not otherwise used. 11 | var _ = proto.Marshal 12 | var _ = fmt.Errorf 13 | var _ = math.Inf 14 | 15 | // This is a compile-time assertion to ensure that this generated file 16 | // is compatible with the proto package it is being compiled against. 17 | // A compilation error at this line likely means your copy of the 18 | // proto package needs to be updated. 19 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 20 | 21 | type Usage struct { 22 | // service name 23 | Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` 24 | // version of service 25 | Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` 26 | // unique service id 27 | Id string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"` 28 | // unix timestamp of report 29 | Timestamp uint64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` 30 | // window of report in seconds 31 | Window uint64 `protobuf:"varint,5,opt,name=window,proto3" json:"window,omitempty"` 32 | // usage metrics 33 | Metrics *Metrics `protobuf:"bytes,6,opt,name=metrics,proto3" json:"metrics,omitempty"` 34 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 35 | XXX_unrecognized []byte `json:"-"` 36 | XXX_sizecache int32 `json:"-"` 37 | } 38 | 39 | func (m *Usage) Reset() { *m = Usage{} } 40 | func (m *Usage) String() string { return proto.CompactTextString(m) } 41 | func (*Usage) ProtoMessage() {} 42 | func (*Usage) Descriptor() ([]byte, []int) { 43 | return fileDescriptor_usage_7cdccb5f3bba2052, []int{0} 44 | } 45 | func (m *Usage) XXX_Unmarshal(b []byte) error { 46 | return xxx_messageInfo_Usage.Unmarshal(m, b) 47 | } 48 | func (m *Usage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 49 | return xxx_messageInfo_Usage.Marshal(b, m, deterministic) 50 | } 51 | func (dst *Usage) XXX_Merge(src proto.Message) { 52 | xxx_messageInfo_Usage.Merge(dst, src) 53 | } 54 | func (m *Usage) XXX_Size() int { 55 | return xxx_messageInfo_Usage.Size(m) 56 | } 57 | func (m *Usage) XXX_DiscardUnknown() { 58 | xxx_messageInfo_Usage.DiscardUnknown(m) 59 | } 60 | 61 | var xxx_messageInfo_Usage proto.InternalMessageInfo 62 | 63 | func (m *Usage) GetService() string { 64 | if m != nil { 65 | return m.Service 66 | } 67 | return "" 68 | } 69 | 70 | func (m *Usage) GetVersion() string { 71 | if m != nil { 72 | return m.Version 73 | } 74 | return "" 75 | } 76 | 77 | func (m *Usage) GetId() string { 78 | if m != nil { 79 | return m.Id 80 | } 81 | return "" 82 | } 83 | 84 | func (m *Usage) GetTimestamp() uint64 { 85 | if m != nil { 86 | return m.Timestamp 87 | } 88 | return 0 89 | } 90 | 91 | func (m *Usage) GetWindow() uint64 { 92 | if m != nil { 93 | return m.Window 94 | } 95 | return 0 96 | } 97 | 98 | func (m *Usage) GetMetrics() *Metrics { 99 | if m != nil { 100 | return m.Metrics 101 | } 102 | return nil 103 | } 104 | 105 | type Metrics struct { 106 | // counts such as requests, services, etc 107 | Count map[string]uint64 `protobuf:"bytes,1,rep,name=count,proto3" json:"count,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` 108 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 109 | XXX_unrecognized []byte `json:"-"` 110 | XXX_sizecache int32 `json:"-"` 111 | } 112 | 113 | func (m *Metrics) Reset() { *m = Metrics{} } 114 | func (m *Metrics) String() string { return proto.CompactTextString(m) } 115 | func (*Metrics) ProtoMessage() {} 116 | func (*Metrics) Descriptor() ([]byte, []int) { 117 | return fileDescriptor_usage_7cdccb5f3bba2052, []int{1} 118 | } 119 | func (m *Metrics) XXX_Unmarshal(b []byte) error { 120 | return xxx_messageInfo_Metrics.Unmarshal(m, b) 121 | } 122 | func (m *Metrics) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 123 | return xxx_messageInfo_Metrics.Marshal(b, m, deterministic) 124 | } 125 | func (dst *Metrics) XXX_Merge(src proto.Message) { 126 | xxx_messageInfo_Metrics.Merge(dst, src) 127 | } 128 | func (m *Metrics) XXX_Size() int { 129 | return xxx_messageInfo_Metrics.Size(m) 130 | } 131 | func (m *Metrics) XXX_DiscardUnknown() { 132 | xxx_messageInfo_Metrics.DiscardUnknown(m) 133 | } 134 | 135 | var xxx_messageInfo_Metrics proto.InternalMessageInfo 136 | 137 | func (m *Metrics) GetCount() map[string]uint64 { 138 | if m != nil { 139 | return m.Count 140 | } 141 | return nil 142 | } 143 | 144 | func init() { 145 | proto.RegisterType((*Usage)(nil), "Usage") 146 | proto.RegisterType((*Metrics)(nil), "Metrics") 147 | proto.RegisterMapType((map[string]uint64)(nil), "Metrics.CountEntry") 148 | } 149 | 150 | func init() { 151 | proto.RegisterFile("github.com/micro/micro/internal/usage/proto/usage.proto", fileDescriptor_usage_7cdccb5f3bba2052) 152 | } 153 | 154 | var fileDescriptor_usage_7cdccb5f3bba2052 = []byte{ 155 | // 251 bytes of a gzipped FileDescriptorProto 156 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x44, 0x90, 0xbd, 0x6a, 0xc3, 0x30, 157 | 0x14, 0x85, 0x91, 0xe3, 0x9f, 0xe6, 0x06, 0x4a, 0x51, 0x4b, 0x11, 0xa5, 0x83, 0xf1, 0xe4, 0x2e, 158 | 0x36, 0xa4, 0x43, 0x43, 0xd7, 0xd2, 0xb1, 0x8b, 0xa1, 0x0f, 0xe0, 0xc8, 0x22, 0xbd, 0x34, 0x92, 159 | 0x82, 0x24, 0x3b, 0xe4, 0x81, 0xfa, 0x9e, 0xc5, 0x92, 0x4c, 0x16, 0x71, 0xbe, 0xef, 0xdc, 0xe5, 160 | 0x08, 0xde, 0x0e, 0xe8, 0x7e, 0xc6, 0x7d, 0xc3, 0xb5, 0x6c, 0x25, 0x72, 0xa3, 0xe3, 0x8b, 0xca, 161 | 0x09, 0xa3, 0xfa, 0x63, 0x3b, 0xda, 0xfe, 0x20, 0xda, 0x93, 0xd1, 0x4e, 0x87, 0xdc, 0xf8, 0x5c, 162 | 0xfd, 0x11, 0xc8, 0xbe, 0x67, 0xa6, 0x0c, 0x0a, 0x2b, 0xcc, 0x84, 0x5c, 0x30, 0x52, 0x92, 0x7a, 163 | 0xdd, 0x2d, 0x38, 0x37, 0x93, 0x30, 0x16, 0xb5, 0x62, 0x49, 0x68, 0x22, 0xd2, 0x5b, 0x48, 0x70, 164 | 0x60, 0x2b, 0x2f, 0x13, 0x1c, 0xe8, 0x33, 0xac, 0x1d, 0x4a, 0x61, 0x5d, 0x2f, 0x4f, 0x2c, 0x2d, 165 | 0x49, 0x9d, 0x76, 0x57, 0x41, 0x1f, 0x21, 0x3f, 0xa3, 0x1a, 0xf4, 0x99, 0x65, 0xbe, 0x8a, 0x44, 166 | 0x2b, 0x28, 0xa4, 0x70, 0x06, 0xb9, 0x65, 0x79, 0x49, 0xea, 0xcd, 0xf6, 0xa6, 0xf9, 0x0a, 0xdc, 167 | 0x2d, 0x45, 0xa5, 0xa0, 0x88, 0x8e, 0xbe, 0x40, 0xc6, 0xf5, 0xa8, 0x1c, 0x23, 0xe5, 0xaa, 0xde, 168 | 0x6c, 0xef, 0x97, 0xe3, 0xe6, 0x63, 0xb6, 0x9f, 0xca, 0x99, 0x4b, 0x17, 0x2e, 0x9e, 0x76, 0x00, 169 | 0x57, 0x49, 0xef, 0x60, 0xf5, 0x2b, 0x2e, 0x71, 0xdd, 0x1c, 0xe9, 0x03, 0x64, 0x53, 0x7f, 0x1c, 170 | 0x85, 0xdf, 0x95, 0x76, 0x01, 0xde, 0x93, 0x1d, 0xd9, 0xe7, 0xfe, 0x7b, 0x5e, 0xff, 0x03, 0x00, 171 | 0x00, 0xff, 0xff, 0x4f, 0xfc, 0xa1, 0x6c, 0x59, 0x01, 0x00, 0x00, 172 | } 173 | -------------------------------------------------------------------------------- /api/api.go: -------------------------------------------------------------------------------- 1 | // Package api is an API Gateway 2 | package api 3 | 4 | import ( 5 | "fmt" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gorilla/mux" 10 | "github.com/micro/cli" 11 | ahandler "github.com/micro/go-api/handler" 12 | aapi "github.com/micro/go-api/handler/api" 13 | "github.com/micro/go-api/handler/event" 14 | ahttp "github.com/micro/go-api/handler/http" 15 | arpc "github.com/micro/go-api/handler/rpc" 16 | "github.com/micro/go-api/handler/web" 17 | "github.com/micro/go-api/resolver" 18 | "github.com/micro/go-api/resolver/grpc" 19 | "github.com/micro/go-api/resolver/host" 20 | rrmicro "github.com/micro/go-api/resolver/micro" 21 | "github.com/micro/go-api/resolver/path" 22 | "github.com/micro/go-api/router" 23 | regRouter "github.com/micro/go-api/router/registry" 24 | "github.com/micro/go-api/server" 25 | httpapi "github.com/micro/go-api/server/http" 26 | "github.com/micro/go-log" 27 | "github.com/micro/go-micro" 28 | "github.com/micro/micro/internal/handler" 29 | "github.com/micro/micro/internal/helper" 30 | "github.com/micro/micro/internal/stats" 31 | "github.com/micro/micro/plugin" 32 | ) 33 | 34 | var ( 35 | Name = "go.micro.api" 36 | Address = ":8080" 37 | Handler = "meta" 38 | Resolver = "micro" 39 | RPCPath = "/rpc" 40 | APIPath = "/" 41 | ProxyPath = "/{service:[a-zA-Z0-9]+}" 42 | Namespace = "go.micro.api" 43 | HeaderPrefix = "X-Micro-" 44 | ) 45 | 46 | func run(ctx *cli.Context, srvOpts ...micro.Option) { 47 | if len(ctx.GlobalString("server_name")) > 0 { 48 | Name = ctx.GlobalString("server_name") 49 | } 50 | if len(ctx.String("address")) > 0 { 51 | Address = ctx.String("address") 52 | } 53 | if len(ctx.String("handler")) > 0 { 54 | Handler = ctx.String("handler") 55 | } 56 | if len(ctx.String("namespace")) > 0 { 57 | Namespace = ctx.String("namespace") 58 | } 59 | if len(ctx.String("resolver")) > 0 { 60 | Resolver = ctx.String("resolver") 61 | } 62 | 63 | // Init plugins 64 | for _, p := range Plugins() { 65 | p.Init(ctx) 66 | } 67 | 68 | // Init API 69 | var opts []server.Option 70 | 71 | if ctx.GlobalBool("enable_acme") { 72 | hosts := helper.ACMEHosts(ctx) 73 | opts = append(opts, server.EnableACME(true)) 74 | opts = append(opts, server.ACMEHosts(hosts...)) 75 | } else if ctx.GlobalBool("enable_tls") { 76 | config, err := helper.TLSConfig(ctx) 77 | if err != nil { 78 | fmt.Println(err.Error()) 79 | return 80 | } 81 | 82 | opts = append(opts, server.EnableTLS(true)) 83 | opts = append(opts, server.TLSConfig(config)) 84 | } 85 | 86 | // create the router 87 | var h http.Handler 88 | r := mux.NewRouter() 89 | h = r 90 | 91 | if ctx.GlobalBool("enable_stats") { 92 | st := stats.New() 93 | r.HandleFunc("/stats", st.StatsHandler) 94 | h = st.ServeHTTP(r) 95 | st.Start() 96 | defer st.Stop() 97 | } 98 | 99 | srvOpts = append(srvOpts, micro.Name(Name)) 100 | if i := time.Duration(ctx.GlobalInt("register_ttl")); i > 0 { 101 | srvOpts = append(srvOpts, micro.RegisterTTL(i*time.Second)) 102 | } 103 | if i := time.Duration(ctx.GlobalInt("register_interval")); i > 0 { 104 | srvOpts = append(srvOpts, micro.RegisterInterval(i*time.Second)) 105 | } 106 | 107 | // initialise service 108 | service := micro.NewService(srvOpts...) 109 | 110 | // register rpc handler 111 | log.Logf("Registering RPC Handler at %s", RPCPath) 112 | r.HandleFunc(RPCPath, handler.RPC) 113 | 114 | // resolver options 115 | ropts := []resolver.Option{ 116 | resolver.WithNamespace(Namespace), 117 | resolver.WithHandler(Handler), 118 | } 119 | 120 | // default resolver 121 | rr := rrmicro.NewResolver(ropts...) 122 | 123 | switch Resolver { 124 | case "host": 125 | rr = host.NewResolver(ropts...) 126 | case "path": 127 | rr = path.NewResolver(ropts...) 128 | case "grpc": 129 | rr = grpc.NewResolver(ropts...) 130 | } 131 | 132 | switch Handler { 133 | case "rpc": 134 | log.Logf("Registering API RPC Handler at %s", APIPath) 135 | rt := regRouter.NewRouter( 136 | router.WithNamespace(Namespace), 137 | router.WithHandler(arpc.Handler), 138 | router.WithResolver(rr), 139 | router.WithRegistry(service.Options().Registry), 140 | ) 141 | rp := arpc.NewHandler( 142 | ahandler.WithNamespace(Namespace), 143 | ahandler.WithRouter(rt), 144 | ahandler.WithService(service), 145 | ) 146 | r.PathPrefix(APIPath).Handler(rp) 147 | case "api": 148 | log.Logf("Registering API Request Handler at %s", APIPath) 149 | rt := regRouter.NewRouter( 150 | router.WithNamespace(Namespace), 151 | router.WithHandler(aapi.Handler), 152 | router.WithResolver(rr), 153 | router.WithRegistry(service.Options().Registry), 154 | ) 155 | ap := aapi.NewHandler( 156 | ahandler.WithNamespace(Namespace), 157 | ahandler.WithRouter(rt), 158 | ahandler.WithService(service), 159 | ) 160 | r.PathPrefix(APIPath).Handler(ap) 161 | case "event": 162 | log.Logf("Registering API Event Handler at %s", APIPath) 163 | rt := regRouter.NewRouter( 164 | router.WithNamespace(Namespace), 165 | router.WithHandler(event.Handler), 166 | router.WithResolver(rr), 167 | router.WithRegistry(service.Options().Registry), 168 | ) 169 | ev := event.NewHandler( 170 | ahandler.WithNamespace(Namespace), 171 | ahandler.WithRouter(rt), 172 | ahandler.WithService(service), 173 | ) 174 | r.PathPrefix(APIPath).Handler(ev) 175 | case "http", "proxy": 176 | log.Logf("Registering API HTTP Handler at %s", ProxyPath) 177 | rt := regRouter.NewRouter( 178 | router.WithNamespace(Namespace), 179 | router.WithHandler(ahttp.Handler), 180 | router.WithResolver(rr), 181 | router.WithRegistry(service.Options().Registry), 182 | ) 183 | ht := ahttp.NewHandler( 184 | ahandler.WithNamespace(Namespace), 185 | ahandler.WithRouter(rt), 186 | ahandler.WithService(service), 187 | ) 188 | r.PathPrefix(ProxyPath).Handler(ht) 189 | case "web": 190 | log.Logf("Registering API Web Handler at %s", APIPath) 191 | rt := regRouter.NewRouter( 192 | router.WithNamespace(Namespace), 193 | router.WithHandler(web.Handler), 194 | router.WithResolver(rr), 195 | router.WithRegistry(service.Options().Registry), 196 | ) 197 | w := web.NewHandler( 198 | ahandler.WithNamespace(Namespace), 199 | ahandler.WithRouter(rt), 200 | ahandler.WithService(service), 201 | ) 202 | r.PathPrefix(APIPath).Handler(w) 203 | default: 204 | log.Logf("Registering API Default Handler at %s", APIPath) 205 | rt := regRouter.NewRouter( 206 | router.WithNamespace(Namespace), 207 | router.WithResolver(rr), 208 | router.WithRegistry(service.Options().Registry), 209 | ) 210 | r.PathPrefix(APIPath).Handler(handler.Meta(service, rt)) 211 | } 212 | 213 | // reverse wrap handler 214 | plugins := append(Plugins(), plugin.Plugins()...) 215 | for i := len(plugins); i > 0; i-- { 216 | h = plugins[i-1].Handler()(h) 217 | } 218 | 219 | // create the server 220 | api := httpapi.NewServer(Address) 221 | api.Init(opts...) 222 | api.Handle("/", h) 223 | 224 | // Start API 225 | if err := api.Start(); err != nil { 226 | log.Fatal(err) 227 | } 228 | 229 | // Run server 230 | if err := service.Run(); err != nil { 231 | log.Fatal(err) 232 | } 233 | 234 | // Stop API 235 | if err := api.Stop(); err != nil { 236 | log.Fatal(err) 237 | } 238 | } 239 | 240 | func Commands(options ...micro.Option) []cli.Command { 241 | command := cli.Command{ 242 | Name: "api", 243 | Usage: "Run the api gateway", 244 | Action: func(ctx *cli.Context) { 245 | run(ctx, options...) 246 | }, 247 | Flags: []cli.Flag{ 248 | cli.StringFlag{ 249 | Name: "address", 250 | Usage: "Set the api address e.g 0.0.0.0:8080", 251 | EnvVar: "MICRO_API_ADDRESS", 252 | }, 253 | cli.StringFlag{ 254 | Name: "handler", 255 | Usage: "Specify the request handler to be used for mapping HTTP requests to services; {api, event, http, rpc}", 256 | EnvVar: "MICRO_API_HANDLER", 257 | }, 258 | cli.StringFlag{ 259 | Name: "namespace", 260 | Usage: "Set the namespace used by the API e.g. com.example.api", 261 | EnvVar: "MICRO_API_NAMESPACE", 262 | }, 263 | cli.StringFlag{ 264 | Name: "resolver", 265 | Usage: "Set the hostname resolver used by the API {host, path, grpc}", 266 | EnvVar: "MICRO_API_RESOLVER", 267 | }, 268 | }, 269 | } 270 | 271 | for _, p := range Plugins() { 272 | if cmds := p.Commands(); len(cmds) > 0 { 273 | command.Subcommands = append(command.Subcommands, cmds...) 274 | } 275 | 276 | if flags := p.Flags(); len(flags) > 0 { 277 | command.Flags = append(command.Flags, flags...) 278 | } 279 | } 280 | 281 | return []cli.Command{command} 282 | } 283 | -------------------------------------------------------------------------------- /bot/proto/bot.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: github.com/micro/micro/bot/proto/bot.proto 3 | 4 | /* 5 | Package go_micro_bot is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | github.com/micro/micro/bot/proto/bot.proto 9 | 10 | It has these top-level messages: 11 | HelpRequest 12 | HelpResponse 13 | ExecRequest 14 | ExecResponse 15 | */ 16 | package go_micro_bot 17 | 18 | import proto "github.com/golang/protobuf/proto" 19 | import fmt "fmt" 20 | import math "math" 21 | 22 | import ( 23 | context "golang.org/x/net/context" 24 | grpc "google.golang.org/grpc" 25 | ) 26 | 27 | // Reference imports to suppress errors if they are not otherwise used. 28 | var _ = proto.Marshal 29 | var _ = fmt.Errorf 30 | var _ = math.Inf 31 | 32 | // This is a compile-time assertion to ensure that this generated file 33 | // is compatible with the proto package it is being compiled against. 34 | // A compilation error at this line likely means your copy of the 35 | // proto package needs to be updated. 36 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 37 | 38 | type HelpRequest struct { 39 | } 40 | 41 | func (m *HelpRequest) Reset() { *m = HelpRequest{} } 42 | func (m *HelpRequest) String() string { return proto.CompactTextString(m) } 43 | func (*HelpRequest) ProtoMessage() {} 44 | func (*HelpRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 45 | 46 | type HelpResponse struct { 47 | Usage string `protobuf:"bytes,1,opt,name=usage" json:"usage,omitempty"` 48 | Description string `protobuf:"bytes,2,opt,name=description" json:"description,omitempty"` 49 | } 50 | 51 | func (m *HelpResponse) Reset() { *m = HelpResponse{} } 52 | func (m *HelpResponse) String() string { return proto.CompactTextString(m) } 53 | func (*HelpResponse) ProtoMessage() {} 54 | func (*HelpResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 55 | 56 | func (m *HelpResponse) GetUsage() string { 57 | if m != nil { 58 | return m.Usage 59 | } 60 | return "" 61 | } 62 | 63 | func (m *HelpResponse) GetDescription() string { 64 | if m != nil { 65 | return m.Description 66 | } 67 | return "" 68 | } 69 | 70 | type ExecRequest struct { 71 | Args []string `protobuf:"bytes,1,rep,name=args" json:"args,omitempty"` 72 | } 73 | 74 | func (m *ExecRequest) Reset() { *m = ExecRequest{} } 75 | func (m *ExecRequest) String() string { return proto.CompactTextString(m) } 76 | func (*ExecRequest) ProtoMessage() {} 77 | func (*ExecRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } 78 | 79 | func (m *ExecRequest) GetArgs() []string { 80 | if m != nil { 81 | return m.Args 82 | } 83 | return nil 84 | } 85 | 86 | type ExecResponse struct { 87 | Result []byte `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` 88 | Error string `protobuf:"bytes,2,opt,name=error" json:"error,omitempty"` 89 | } 90 | 91 | func (m *ExecResponse) Reset() { *m = ExecResponse{} } 92 | func (m *ExecResponse) String() string { return proto.CompactTextString(m) } 93 | func (*ExecResponse) ProtoMessage() {} 94 | func (*ExecResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } 95 | 96 | func (m *ExecResponse) GetResult() []byte { 97 | if m != nil { 98 | return m.Result 99 | } 100 | return nil 101 | } 102 | 103 | func (m *ExecResponse) GetError() string { 104 | if m != nil { 105 | return m.Error 106 | } 107 | return "" 108 | } 109 | 110 | func init() { 111 | proto.RegisterType((*HelpRequest)(nil), "go.micro.bot.HelpRequest") 112 | proto.RegisterType((*HelpResponse)(nil), "go.micro.bot.HelpResponse") 113 | proto.RegisterType((*ExecRequest)(nil), "go.micro.bot.ExecRequest") 114 | proto.RegisterType((*ExecResponse)(nil), "go.micro.bot.ExecResponse") 115 | } 116 | 117 | // Reference imports to suppress errors if they are not otherwise used. 118 | var _ context.Context 119 | var _ grpc.ClientConn 120 | 121 | // This is a compile-time assertion to ensure that this generated file 122 | // is compatible with the grpc package it is being compiled against. 123 | const _ = grpc.SupportPackageIsVersion4 124 | 125 | // Client API for Command service 126 | 127 | type CommandClient interface { 128 | Help(ctx context.Context, in *HelpRequest, opts ...grpc.CallOption) (*HelpResponse, error) 129 | Exec(ctx context.Context, in *ExecRequest, opts ...grpc.CallOption) (*ExecResponse, error) 130 | } 131 | 132 | type commandClient struct { 133 | cc *grpc.ClientConn 134 | } 135 | 136 | func NewCommandClient(cc *grpc.ClientConn) CommandClient { 137 | return &commandClient{cc} 138 | } 139 | 140 | func (c *commandClient) Help(ctx context.Context, in *HelpRequest, opts ...grpc.CallOption) (*HelpResponse, error) { 141 | out := new(HelpResponse) 142 | err := grpc.Invoke(ctx, "/go.micro.bot.Command/Help", in, out, c.cc, opts...) 143 | if err != nil { 144 | return nil, err 145 | } 146 | return out, nil 147 | } 148 | 149 | func (c *commandClient) Exec(ctx context.Context, in *ExecRequest, opts ...grpc.CallOption) (*ExecResponse, error) { 150 | out := new(ExecResponse) 151 | err := grpc.Invoke(ctx, "/go.micro.bot.Command/Exec", in, out, c.cc, opts...) 152 | if err != nil { 153 | return nil, err 154 | } 155 | return out, nil 156 | } 157 | 158 | // Server API for Command service 159 | 160 | type CommandServer interface { 161 | Help(context.Context, *HelpRequest) (*HelpResponse, error) 162 | Exec(context.Context, *ExecRequest) (*ExecResponse, error) 163 | } 164 | 165 | func RegisterCommandServer(s *grpc.Server, srv CommandServer) { 166 | s.RegisterService(&_Command_serviceDesc, srv) 167 | } 168 | 169 | func _Command_Help_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 170 | in := new(HelpRequest) 171 | if err := dec(in); err != nil { 172 | return nil, err 173 | } 174 | if interceptor == nil { 175 | return srv.(CommandServer).Help(ctx, in) 176 | } 177 | info := &grpc.UnaryServerInfo{ 178 | Server: srv, 179 | FullMethod: "/go.micro.bot.Command/Help", 180 | } 181 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 182 | return srv.(CommandServer).Help(ctx, req.(*HelpRequest)) 183 | } 184 | return interceptor(ctx, in, info, handler) 185 | } 186 | 187 | func _Command_Exec_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 188 | in := new(ExecRequest) 189 | if err := dec(in); err != nil { 190 | return nil, err 191 | } 192 | if interceptor == nil { 193 | return srv.(CommandServer).Exec(ctx, in) 194 | } 195 | info := &grpc.UnaryServerInfo{ 196 | Server: srv, 197 | FullMethod: "/go.micro.bot.Command/Exec", 198 | } 199 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 200 | return srv.(CommandServer).Exec(ctx, req.(*ExecRequest)) 201 | } 202 | return interceptor(ctx, in, info, handler) 203 | } 204 | 205 | var _Command_serviceDesc = grpc.ServiceDesc{ 206 | ServiceName: "go.micro.bot.Command", 207 | HandlerType: (*CommandServer)(nil), 208 | Methods: []grpc.MethodDesc{ 209 | { 210 | MethodName: "Help", 211 | Handler: _Command_Help_Handler, 212 | }, 213 | { 214 | MethodName: "Exec", 215 | Handler: _Command_Exec_Handler, 216 | }, 217 | }, 218 | Streams: []grpc.StreamDesc{}, 219 | Metadata: "github.com/micro/micro/bot/proto/bot.proto", 220 | } 221 | 222 | func init() { proto.RegisterFile("github.com/micro/micro/bot/proto/bot.proto", fileDescriptor0) } 223 | 224 | var fileDescriptor0 = []byte{ 225 | // 241 bytes of a gzipped FileDescriptorProto 226 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x50, 0xcb, 0x4a, 0xc4, 0x30, 227 | 0x14, 0x9d, 0xea, 0x38, 0x32, 0xb7, 0x75, 0x13, 0x44, 0xea, 0xac, 0x6a, 0x56, 0x83, 0x8b, 0x0c, 228 | 0xe8, 0x56, 0x70, 0x21, 0x8a, 0xeb, 0xfe, 0x41, 0xdb, 0xb9, 0xd4, 0xc2, 0xb4, 0x37, 0xde, 0x24, 229 | 0xe0, 0x3f, 0xf8, 0xd3, 0x92, 0xc7, 0x22, 0x0c, 0x6e, 0xc2, 0x39, 0x39, 0xe1, 0x3c, 0x02, 0x8f, 230 | 0xe3, 0x64, 0xbf, 0x5c, 0xaf, 0x06, 0x9a, 0x0f, 0xf3, 0x34, 0x30, 0xa5, 0xb3, 0x27, 0x7b, 0xd0, 231 | 0x4c, 0x36, 0x20, 0x15, 0x90, 0xa8, 0x46, 0x52, 0x41, 0x55, 0x3d, 0x59, 0x79, 0x03, 0xe5, 0x27, 232 | 0x9e, 0x74, 0x8b, 0xdf, 0x0e, 0x8d, 0x95, 0x1f, 0x50, 0x45, 0x6a, 0x34, 0x2d, 0x06, 0xc5, 0x2d, 233 | 0x5c, 0x39, 0xd3, 0x8d, 0x58, 0x17, 0x4d, 0xb1, 0xdf, 0xb6, 0x91, 0x88, 0x06, 0xca, 0x23, 0x9a, 234 | 0x81, 0x27, 0x6d, 0x27, 0x5a, 0xea, 0x8b, 0xa0, 0xe5, 0x57, 0xf2, 0x01, 0xca, 0xf7, 0x1f, 0x1c, 235 | 0x92, 0xad, 0x10, 0xb0, 0xee, 0x78, 0x34, 0x75, 0xd1, 0x5c, 0xee, 0xb7, 0x6d, 0xc0, 0xf2, 0x05, 236 | 0xaa, 0xf8, 0x24, 0x45, 0xdd, 0xc1, 0x86, 0xd1, 0xb8, 0x93, 0x0d, 0x59, 0x55, 0x9b, 0x98, 0xaf, 237 | 0x80, 0xcc, 0xc4, 0x29, 0x26, 0x92, 0xa7, 0xdf, 0x02, 0xae, 0xdf, 0x68, 0x9e, 0xbb, 0xe5, 0x28, 238 | 0x5e, 0x61, 0xed, 0x4b, 0x8b, 0x7b, 0x95, 0x4f, 0x53, 0xd9, 0xae, 0xdd, 0xee, 0x3f, 0x29, 0x06, 239 | 0xcb, 0x95, 0x37, 0xf0, 0x55, 0xce, 0x0d, 0xb2, 0x05, 0xe7, 0x06, 0x79, 0x73, 0xb9, 0xea, 0x37, 240 | 0xe1, 0x6b, 0x9f, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x15, 0x59, 0x33, 0x88, 0x01, 0x00, 241 | 0x00, 242 | } 243 | -------------------------------------------------------------------------------- /internal/stats/template.go: -------------------------------------------------------------------------------- 1 | package stats 2 | 3 | var ( 4 | layoutTemplate = ` 5 | {{define "layout"}} 6 | 7 | 8 | Micro Stats 9 | 10 | 13 | 14 | 15 | 22 |
23 |
24 | 25 |
26 |

 

27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
Info
Started
Uptime
Memory
Threads
GC
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
Requests
Total
20x
40x
50x
74 |
75 |
76 | {{ template "content" . }} 77 |
78 |
79 |
80 | 81 | 82 | 83 | {{template "script" . }} 84 | 85 | 86 | {{end}} 87 | {{ define "style" }}{{end}} 88 | {{ define "script" }}{{end}} 89 | {{ define "title" }}{{end}} 90 | ` 91 | 92 | statsTemplate = ` 93 | {{define "title"}}Stats{{end}} 94 | {{define "content"}} 95 |
96 | {{end}} 97 | {{define "script"}} 98 | 290 | {{end}} 291 | ` 292 | ) 293 | -------------------------------------------------------------------------------- /api/proto/api.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: github.com/micro/micro/api/proto/api.proto 3 | 4 | package go_micro_api 5 | 6 | import proto "github.com/golang/protobuf/proto" 7 | import fmt "fmt" 8 | import math "math" 9 | 10 | // Reference imports to suppress errors if they are not otherwise used. 11 | var _ = proto.Marshal 12 | var _ = fmt.Errorf 13 | var _ = math.Inf 14 | 15 | // This is a compile-time assertion to ensure that this generated file 16 | // is compatible with the proto package it is being compiled against. 17 | // A compilation error at this line likely means your copy of the 18 | // proto package needs to be updated. 19 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 20 | 21 | type Pair struct { 22 | Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` 23 | Values []string `protobuf:"bytes,2,rep,name=values,proto3" json:"values,omitempty"` 24 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 25 | XXX_unrecognized []byte `json:"-"` 26 | XXX_sizecache int32 `json:"-"` 27 | } 28 | 29 | func (m *Pair) Reset() { *m = Pair{} } 30 | func (m *Pair) String() string { return proto.CompactTextString(m) } 31 | func (*Pair) ProtoMessage() {} 32 | func (*Pair) Descriptor() ([]byte, []int) { 33 | return fileDescriptor_api_f8ef31c2e6dafc0b, []int{0} 34 | } 35 | func (m *Pair) XXX_Unmarshal(b []byte) error { 36 | return xxx_messageInfo_Pair.Unmarshal(m, b) 37 | } 38 | func (m *Pair) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 39 | return xxx_messageInfo_Pair.Marshal(b, m, deterministic) 40 | } 41 | func (dst *Pair) XXX_Merge(src proto.Message) { 42 | xxx_messageInfo_Pair.Merge(dst, src) 43 | } 44 | func (m *Pair) XXX_Size() int { 45 | return xxx_messageInfo_Pair.Size(m) 46 | } 47 | func (m *Pair) XXX_DiscardUnknown() { 48 | xxx_messageInfo_Pair.DiscardUnknown(m) 49 | } 50 | 51 | var xxx_messageInfo_Pair proto.InternalMessageInfo 52 | 53 | func (m *Pair) GetKey() string { 54 | if m != nil { 55 | return m.Key 56 | } 57 | return "" 58 | } 59 | 60 | func (m *Pair) GetValues() []string { 61 | if m != nil { 62 | return m.Values 63 | } 64 | return nil 65 | } 66 | 67 | type Request struct { 68 | Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` 69 | Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` 70 | Header map[string]*Pair `protobuf:"bytes,3,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` 71 | Get map[string]*Pair `protobuf:"bytes,4,rep,name=get,proto3" json:"get,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` 72 | Post map[string]*Pair `protobuf:"bytes,5,rep,name=post,proto3" json:"post,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` 73 | Body string `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` 74 | Url string `protobuf:"bytes,7,opt,name=url,proto3" json:"url,omitempty"` 75 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 76 | XXX_unrecognized []byte `json:"-"` 77 | XXX_sizecache int32 `json:"-"` 78 | } 79 | 80 | func (m *Request) Reset() { *m = Request{} } 81 | func (m *Request) String() string { return proto.CompactTextString(m) } 82 | func (*Request) ProtoMessage() {} 83 | func (*Request) Descriptor() ([]byte, []int) { 84 | return fileDescriptor_api_f8ef31c2e6dafc0b, []int{1} 85 | } 86 | func (m *Request) XXX_Unmarshal(b []byte) error { 87 | return xxx_messageInfo_Request.Unmarshal(m, b) 88 | } 89 | func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 90 | return xxx_messageInfo_Request.Marshal(b, m, deterministic) 91 | } 92 | func (dst *Request) XXX_Merge(src proto.Message) { 93 | xxx_messageInfo_Request.Merge(dst, src) 94 | } 95 | func (m *Request) XXX_Size() int { 96 | return xxx_messageInfo_Request.Size(m) 97 | } 98 | func (m *Request) XXX_DiscardUnknown() { 99 | xxx_messageInfo_Request.DiscardUnknown(m) 100 | } 101 | 102 | var xxx_messageInfo_Request proto.InternalMessageInfo 103 | 104 | func (m *Request) GetMethod() string { 105 | if m != nil { 106 | return m.Method 107 | } 108 | return "" 109 | } 110 | 111 | func (m *Request) GetPath() string { 112 | if m != nil { 113 | return m.Path 114 | } 115 | return "" 116 | } 117 | 118 | func (m *Request) GetHeader() map[string]*Pair { 119 | if m != nil { 120 | return m.Header 121 | } 122 | return nil 123 | } 124 | 125 | func (m *Request) GetGet() map[string]*Pair { 126 | if m != nil { 127 | return m.Get 128 | } 129 | return nil 130 | } 131 | 132 | func (m *Request) GetPost() map[string]*Pair { 133 | if m != nil { 134 | return m.Post 135 | } 136 | return nil 137 | } 138 | 139 | func (m *Request) GetBody() string { 140 | if m != nil { 141 | return m.Body 142 | } 143 | return "" 144 | } 145 | 146 | func (m *Request) GetUrl() string { 147 | if m != nil { 148 | return m.Url 149 | } 150 | return "" 151 | } 152 | 153 | type Response struct { 154 | StatusCode int32 `protobuf:"varint,1,opt,name=statusCode,proto3" json:"statusCode,omitempty"` 155 | Header map[string]*Pair `protobuf:"bytes,2,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` 156 | Body string `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"` 157 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 158 | XXX_unrecognized []byte `json:"-"` 159 | XXX_sizecache int32 `json:"-"` 160 | } 161 | 162 | func (m *Response) Reset() { *m = Response{} } 163 | func (m *Response) String() string { return proto.CompactTextString(m) } 164 | func (*Response) ProtoMessage() {} 165 | func (*Response) Descriptor() ([]byte, []int) { 166 | return fileDescriptor_api_f8ef31c2e6dafc0b, []int{2} 167 | } 168 | func (m *Response) XXX_Unmarshal(b []byte) error { 169 | return xxx_messageInfo_Response.Unmarshal(m, b) 170 | } 171 | func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 172 | return xxx_messageInfo_Response.Marshal(b, m, deterministic) 173 | } 174 | func (dst *Response) XXX_Merge(src proto.Message) { 175 | xxx_messageInfo_Response.Merge(dst, src) 176 | } 177 | func (m *Response) XXX_Size() int { 178 | return xxx_messageInfo_Response.Size(m) 179 | } 180 | func (m *Response) XXX_DiscardUnknown() { 181 | xxx_messageInfo_Response.DiscardUnknown(m) 182 | } 183 | 184 | var xxx_messageInfo_Response proto.InternalMessageInfo 185 | 186 | func (m *Response) GetStatusCode() int32 { 187 | if m != nil { 188 | return m.StatusCode 189 | } 190 | return 0 191 | } 192 | 193 | func (m *Response) GetHeader() map[string]*Pair { 194 | if m != nil { 195 | return m.Header 196 | } 197 | return nil 198 | } 199 | 200 | func (m *Response) GetBody() string { 201 | if m != nil { 202 | return m.Body 203 | } 204 | return "" 205 | } 206 | 207 | func init() { 208 | proto.RegisterType((*Pair)(nil), "go.micro.api.Pair") 209 | proto.RegisterType((*Request)(nil), "go.micro.api.Request") 210 | proto.RegisterMapType((map[string]*Pair)(nil), "go.micro.api.Request.GetEntry") 211 | proto.RegisterMapType((map[string]*Pair)(nil), "go.micro.api.Request.HeaderEntry") 212 | proto.RegisterMapType((map[string]*Pair)(nil), "go.micro.api.Request.PostEntry") 213 | proto.RegisterType((*Response)(nil), "go.micro.api.Response") 214 | proto.RegisterMapType((map[string]*Pair)(nil), "go.micro.api.Response.HeaderEntry") 215 | } 216 | 217 | func init() { 218 | proto.RegisterFile("github.com/micro/micro/api/proto/api.proto", fileDescriptor_api_f8ef31c2e6dafc0b) 219 | } 220 | 221 | var fileDescriptor_api_f8ef31c2e6dafc0b = []byte{ 222 | // 347 bytes of a gzipped FileDescriptorProto 223 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x92, 0xcd, 0x6e, 0xe2, 0x30, 224 | 0x14, 0x85, 0x95, 0x1f, 0x02, 0x5c, 0x66, 0x31, 0xf2, 0x62, 0x64, 0xb1, 0x60, 0x98, 0xac, 0xa2, 225 | 0x59, 0x18, 0x04, 0x9b, 0x96, 0x6d, 0x55, 0xb5, 0x6a, 0x55, 0x09, 0xf9, 0x0d, 0x0c, 0xb1, 0x48, 226 | 0x54, 0xc0, 0xa9, 0xed, 0x54, 0xe2, 0x11, 0xfb, 0x20, 0x7d, 0x8f, 0xca, 0x37, 0x86, 0xd2, 0x2a, 227 | 0x3b, 0xba, 0x89, 0xae, 0x9d, 0x73, 0x4e, 0x8e, 0x3f, 0x07, 0xfe, 0x6f, 0x4a, 0x5b, 0xd4, 0x2b, 228 | 0xb6, 0x56, 0xbb, 0xc9, 0xae, 0x5c, 0x6b, 0xe5, 0x9f, 0xa2, 0x2a, 0x27, 0x95, 0x56, 0x16, 0x27, 229 | 0x86, 0x13, 0xf9, 0xb5, 0x51, 0x0c, 0xdf, 0x32, 0x51, 0x95, 0xe9, 0x14, 0xe2, 0xa5, 0x28, 0x35, 230 | 0xf9, 0x0d, 0xd1, 0xb3, 0x3c, 0xd0, 0x60, 0x1c, 0x64, 0x7d, 0xee, 0x46, 0xf2, 0x07, 0x92, 0x57, 231 | 0xb1, 0xad, 0xa5, 0xa1, 0xe1, 0x38, 0xca, 0xfa, 0xdc, 0xaf, 0xd2, 0xf7, 0x08, 0xba, 0x5c, 0xbe, 232 | 0xd4, 0xd2, 0x58, 0xa7, 0xd9, 0x49, 0x5b, 0xa8, 0xdc, 0x1b, 0xfd, 0x8a, 0x10, 0x88, 0x2b, 0x61, 233 | 0x0b, 0x1a, 0xe2, 0x2e, 0xce, 0xe4, 0x1a, 0x92, 0x42, 0x8a, 0x5c, 0x6a, 0x1a, 0x8d, 0xa3, 0x6c, 234 | 0x30, 0xfb, 0xc7, 0xce, 0x8b, 0x30, 0x1f, 0xc9, 0xee, 0x51, 0x73, 0xbb, 0xb7, 0xfa, 0xc0, 0xbd, 235 | 0x81, 0x4c, 0x21, 0xda, 0x48, 0x4b, 0x63, 0xf4, 0x8d, 0xda, 0x7d, 0x77, 0xd2, 0x36, 0x26, 0x27, 236 | 0x25, 0x73, 0x88, 0x2b, 0x65, 0x2c, 0xed, 0xa0, 0xe5, 0x6f, 0xbb, 0x65, 0xa9, 0x8c, 0xf7, 0xa0, 237 | 0xd8, 0xb5, 0x5e, 0xa9, 0xfc, 0x40, 0x93, 0xa6, 0xb5, 0x9b, 0x1d, 0x97, 0x5a, 0x6f, 0x69, 0xb7, 238 | 0xe1, 0x52, 0xeb, 0xed, 0xf0, 0x09, 0x06, 0x67, 0x1d, 0x5b, 0xc0, 0x65, 0xd0, 0x41, 0x54, 0x78, 239 | 0xfa, 0xc1, 0x8c, 0x7c, 0xfd, 0xb8, 0xa3, 0xcd, 0x1b, 0xc1, 0x22, 0xbc, 0x0a, 0x86, 0x0f, 0xd0, 240 | 0x3b, 0x56, 0xbf, 0x38, 0xeb, 0x11, 0xfa, 0xa7, 0x33, 0x5d, 0x1a, 0x96, 0xbe, 0x05, 0xd0, 0xe3, 241 | 0xd2, 0x54, 0x6a, 0x6f, 0x24, 0x19, 0x01, 0x18, 0x2b, 0x6c, 0x6d, 0x6e, 0x54, 0x2e, 0x31, 0xb3, 242 | 0xc3, 0xcf, 0x76, 0xc8, 0xe2, 0x74, 0xb9, 0x21, 0x12, 0x4f, 0xbf, 0x13, 0x6f, 0x72, 0x5a, 0x6f, 243 | 0xf7, 0x88, 0x3d, 0xfa, 0xc4, 0xfe, 0xc3, 0x90, 0x57, 0x09, 0xfe, 0xfa, 0xf3, 0x8f, 0x00, 0x00, 244 | 0x00, 0xff, 0xff, 0x7b, 0x71, 0xca, 0x17, 0x28, 0x03, 0x00, 0x00, 245 | } 246 | -------------------------------------------------------------------------------- /new/new.go: -------------------------------------------------------------------------------- 1 | // Package new generates micro service templates 2 | package new 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "path" 8 | "path/filepath" 9 | "runtime" 10 | "strings" 11 | "text/template" 12 | "time" 13 | 14 | "github.com/micro/cli" 15 | tmpl "github.com/micro/micro/internal/template" 16 | "github.com/micro/micro/internal/usage" 17 | "github.com/xlab/treeprint" 18 | ) 19 | 20 | type config struct { 21 | // foo 22 | Alias string 23 | // micro new example -type 24 | Command string 25 | // go.micro 26 | Namespace string 27 | // api, srv, web, fnc 28 | Type string 29 | // go.micro.srv.foo 30 | FQDN string 31 | // github.com/micro/foo 32 | Dir string 33 | // $GOPATH/src/github.com/micro/foo 34 | GoDir string 35 | // $GOPATH 36 | GoPath string 37 | // Files 38 | Files []file 39 | // Comments 40 | Comments []string 41 | // Plugins registry=etcd:broker=nats 42 | Plugins []string 43 | } 44 | 45 | type file struct { 46 | Path string 47 | Tmpl string 48 | } 49 | 50 | func write(c config, file, tmpl string) error { 51 | fn := template.FuncMap{ 52 | "title": strings.Title, 53 | } 54 | 55 | f, err := os.Create(file) 56 | if err != nil { 57 | return err 58 | } 59 | defer f.Close() 60 | 61 | t, err := template.New("f").Funcs(fn).Parse(tmpl) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | return t.Execute(f, c) 67 | } 68 | 69 | func create(c config) error { 70 | // check if dir exists 71 | if _, err := os.Stat(c.GoDir); !os.IsNotExist(err) { 72 | return fmt.Errorf("%s already exists", c.GoDir) 73 | } 74 | 75 | // create usage report 76 | u := usage.New("new") 77 | // a single request/service 78 | u.Metrics.Count["requests"] = uint64(1) 79 | u.Metrics.Count["services"] = uint64(1) 80 | // send report 81 | go usage.Report(u) 82 | 83 | // just wait 84 | <-time.After(time.Millisecond * 250) 85 | 86 | fmt.Printf("Creating service %s in %s\n\n", c.FQDN, c.GoDir) 87 | 88 | t := treeprint.New() 89 | 90 | nodes := map[string]treeprint.Tree{} 91 | nodes[c.GoDir] = t 92 | 93 | // write the files 94 | for _, file := range c.Files { 95 | f := filepath.Join(c.GoDir, file.Path) 96 | dir := filepath.Dir(f) 97 | 98 | b, ok := nodes[dir] 99 | if !ok { 100 | d, _ := filepath.Rel(c.GoDir, dir) 101 | b = t.AddBranch(d) 102 | nodes[dir] = b 103 | } 104 | 105 | if _, err := os.Stat(dir); os.IsNotExist(err) { 106 | if err := os.MkdirAll(dir, 0755); err != nil { 107 | return err 108 | } 109 | } 110 | 111 | p := filepath.Base(f) 112 | 113 | b.AddNode(p) 114 | if err := write(c, f, file.Tmpl); err != nil { 115 | return err 116 | } 117 | } 118 | 119 | // print tree 120 | fmt.Println(t.String()) 121 | 122 | for _, comment := range c.Comments { 123 | fmt.Println(comment) 124 | } 125 | 126 | // just wait 127 | <-time.After(time.Millisecond * 250) 128 | 129 | return nil 130 | } 131 | 132 | func run(ctx *cli.Context) { 133 | namespace := ctx.String("namespace") 134 | alias := ctx.String("alias") 135 | fqdn := ctx.String("fqdn") 136 | atype := ctx.String("type") 137 | dir := ctx.Args().First() 138 | useGoPath := ctx.Bool("gopath") 139 | useGoModule := os.Getenv("GO111MODULE") 140 | var plugins []string 141 | 142 | if len(dir) == 0 { 143 | fmt.Println("specify service name") 144 | return 145 | } 146 | 147 | if len(namespace) == 0 { 148 | fmt.Println("namespace not defined") 149 | return 150 | } 151 | 152 | if len(atype) == 0 { 153 | fmt.Println("type not defined") 154 | return 155 | } 156 | 157 | // set the command 158 | command := fmt.Sprintf("micro new %s", dir) 159 | if len(namespace) > 0 { 160 | command += " --namespace=" + namespace 161 | } 162 | if len(alias) > 0 { 163 | command += " --alias=" + alias 164 | } 165 | if len(fqdn) > 0 { 166 | command += " --fqdn=" + fqdn 167 | } 168 | if len(atype) > 0 { 169 | command += " --type=" + atype 170 | } 171 | if plugins := ctx.StringSlice("plugin"); len(plugins) > 0 { 172 | command += " --plugin=" + strings.Join(plugins, ":") 173 | } 174 | 175 | // check if the path is absolute, we don't want this 176 | // we want to a relative path so we can install in GOPATH 177 | if path.IsAbs(dir) { 178 | fmt.Println("require relative path as service will be installed in GOPATH") 179 | return 180 | } 181 | 182 | var goPath string 183 | var goDir string 184 | 185 | // only set gopath if told to use it 186 | if useGoPath { 187 | goPath = os.Getenv("GOPATH") 188 | 189 | // don't know GOPATH, runaway.... 190 | if len(goPath) == 0 { 191 | fmt.Println("unknown GOPATH") 192 | return 193 | } 194 | 195 | // attempt to split path if not windows 196 | if runtime.GOOS == "windows" { 197 | goPath = strings.Split(goPath, ";")[0] 198 | } else { 199 | goPath = strings.Split(goPath, ":")[0] 200 | } 201 | goDir = filepath.Join(goPath, "src", path.Clean(dir)) 202 | } else { 203 | goDir = path.Clean(dir) 204 | } 205 | 206 | if len(alias) == 0 { 207 | // set as last part 208 | alias = filepath.Base(dir) 209 | } 210 | 211 | if len(fqdn) == 0 { 212 | fqdn = strings.Join([]string{namespace, atype, alias}, ".") 213 | } 214 | 215 | for _, plugin := range ctx.StringSlice("plugin") { 216 | // registry=etcd:broker=nats 217 | for _, p := range strings.Split(plugin, ":") { 218 | // registry=etcd 219 | parts := strings.Split(p, "=") 220 | if len(parts) < 2 { 221 | continue 222 | } 223 | plugins = append(plugins, path.Join(parts...)) 224 | } 225 | } 226 | 227 | var c config 228 | 229 | switch atype { 230 | case "fnc": 231 | // create srv config 232 | c = config{ 233 | Alias: alias, 234 | Command: command, 235 | Namespace: namespace, 236 | Type: atype, 237 | FQDN: fqdn, 238 | Dir: dir, 239 | GoDir: goDir, 240 | GoPath: goPath, 241 | Plugins: plugins, 242 | Files: []file{ 243 | {"main.go", tmpl.MainFNC}, 244 | {"plugin.go", tmpl.Plugin}, 245 | {"handler/example.go", tmpl.HandlerFNC}, 246 | {"subscriber/example.go", tmpl.SubscriberFNC}, 247 | {"proto/example/example.proto", tmpl.ProtoFNC}, 248 | {"Dockerfile", tmpl.DockerFNC}, 249 | {"Makefile", tmpl.Makefile}, 250 | {"README.md", tmpl.ReadmeFNC}, 251 | }, 252 | Comments: []string{ 253 | "\ndownload protobuf for micro:\n", 254 | "brew install protobuf", 255 | "go get -u github.com/golang/protobuf/{proto,protoc-gen-go}", 256 | "go get -u github.com/micro/protoc-gen-micro", 257 | "\ncompile the proto file example.proto:\n", 258 | "cd " + goDir, 259 | "protoc --proto_path=. --go_out=. --micro_out=. proto/example/example.proto\n", 260 | }, 261 | } 262 | case "srv": 263 | // create srv config 264 | c = config{ 265 | Alias: alias, 266 | Command: command, 267 | Namespace: namespace, 268 | Type: atype, 269 | FQDN: fqdn, 270 | Dir: dir, 271 | GoDir: goDir, 272 | GoPath: goPath, 273 | Plugins: plugins, 274 | Files: []file{ 275 | {"main.go", tmpl.MainSRV}, 276 | {"plugin.go", tmpl.Plugin}, 277 | {"handler/example.go", tmpl.HandlerSRV}, 278 | {"subscriber/example.go", tmpl.SubscriberSRV}, 279 | {"proto/example/example.proto", tmpl.ProtoSRV}, 280 | {"Dockerfile", tmpl.DockerSRV}, 281 | {"Makefile", tmpl.Makefile}, 282 | {"README.md", tmpl.Readme}, 283 | }, 284 | Comments: []string{ 285 | "\ndownload protobuf for micro:\n", 286 | "brew install protobuf", 287 | "go get -u github.com/golang/protobuf/{proto,protoc-gen-go}", 288 | "go get -u github.com/micro/protoc-gen-micro", 289 | "\ncompile the proto file example.proto:\n", 290 | "cd " + goDir, 291 | "protoc --proto_path=. --go_out=. --micro_out=. proto/example/example.proto\n", 292 | }, 293 | } 294 | case "api": 295 | // create api config 296 | c = config{ 297 | Alias: alias, 298 | Command: command, 299 | Namespace: namespace, 300 | Type: atype, 301 | FQDN: fqdn, 302 | Dir: dir, 303 | GoDir: goDir, 304 | GoPath: goPath, 305 | Plugins: plugins, 306 | Files: []file{ 307 | {"main.go", tmpl.MainAPI}, 308 | {"plugin.go", tmpl.Plugin}, 309 | {"client/example.go", tmpl.WrapperAPI}, 310 | {"handler/example.go", tmpl.HandlerAPI}, 311 | {"proto/example/example.proto", tmpl.ProtoAPI}, 312 | {"Makefile", tmpl.Makefile}, 313 | {"Dockerfile", tmpl.DockerSRV}, 314 | {"README.md", tmpl.Readme}, 315 | }, 316 | Comments: []string{ 317 | "\ndownload protobuf for micro:\n", 318 | "brew install protobuf", 319 | "go get -u github.com/golang/protobuf/{proto,protoc-gen-go}", 320 | "go get -u github.com/micro/protoc-gen-micro", 321 | "\ncompile the proto file example.proto:\n", 322 | "cd " + goDir, 323 | "protoc --proto_path=. --go_out=. --micro_out=. proto/example/example.proto\n", 324 | }, 325 | } 326 | case "web": 327 | // create srv config 328 | c = config{ 329 | Alias: alias, 330 | Command: command, 331 | Namespace: namespace, 332 | Type: atype, 333 | FQDN: fqdn, 334 | Dir: dir, 335 | GoDir: goDir, 336 | GoPath: goPath, 337 | Plugins: plugins, 338 | Files: []file{ 339 | {"main.go", tmpl.MainWEB}, 340 | {"plugin.go", tmpl.Plugin}, 341 | {"handler/handler.go", tmpl.HandlerWEB}, 342 | {"html/index.html", tmpl.HTMLWEB}, 343 | {"Dockerfile", tmpl.DockerWEB}, 344 | {"Makefile", tmpl.Makefile}, 345 | {"README.md", tmpl.Readme}, 346 | }, 347 | Comments: []string{}, 348 | } 349 | default: 350 | fmt.Println("Unknown type", atype) 351 | return 352 | } 353 | 354 | // set gomodule 355 | if useGoModule == "on" || useGoModule == "auto" { 356 | c.Files = append(c.Files, file{"go.mod", tmpl.Module}) 357 | } 358 | 359 | if err := create(c); err != nil { 360 | fmt.Println(err) 361 | return 362 | } 363 | } 364 | 365 | func Commands() []cli.Command { 366 | return []cli.Command{ 367 | { 368 | Name: "new", 369 | Usage: "Create a service template", 370 | Flags: []cli.Flag{ 371 | cli.StringFlag{ 372 | Name: "namespace", 373 | Usage: "Namespace for the service e.g com.example", 374 | Value: "go.micro", 375 | }, 376 | cli.StringFlag{ 377 | Name: "type", 378 | Usage: "Type of service e.g api, fnc, srv, web", 379 | Value: "srv", 380 | }, 381 | cli.StringFlag{ 382 | Name: "fqdn", 383 | Usage: "FQDN of service e.g com.example.srv.service (defaults to namespace.type.alias)", 384 | }, 385 | cli.StringFlag{ 386 | Name: "alias", 387 | Usage: "Alias is the short name used as part of combined name if specified", 388 | }, 389 | cli.StringSliceFlag{ 390 | Name: "plugin", 391 | Usage: "Specify plugins e.g --plugin=registry=etcd:broker=nats or use flag multiple times", 392 | }, 393 | cli.BoolTFlag{ 394 | Name: "gopath", 395 | Usage: "Create the service in the gopath. Defaults to true.", 396 | }, 397 | }, 398 | Action: func(c *cli.Context) { 399 | run(c) 400 | }, 401 | }, 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2015 Asim Aslam. 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /web/web.go: -------------------------------------------------------------------------------- 1 | // Package web is a web dashboard 2 | package web 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "html/template" 8 | "net/http" 9 | "net/http/httputil" 10 | "regexp" 11 | "sort" 12 | "strings" 13 | "time" 14 | 15 | "github.com/gorilla/mux" 16 | "github.com/micro/cli" 17 | "github.com/micro/go-api/server" 18 | httpapi "github.com/micro/go-api/server/http" 19 | "github.com/micro/go-log" 20 | "github.com/micro/go-micro" 21 | "github.com/micro/go-micro/cmd" 22 | "github.com/micro/go-micro/registry" 23 | "github.com/micro/go-micro/selector" 24 | "github.com/micro/micro/internal/handler" 25 | "github.com/micro/micro/internal/helper" 26 | "github.com/micro/micro/internal/stats" 27 | "github.com/micro/micro/plugin" 28 | "github.com/serenize/snaker" 29 | ) 30 | 31 | var ( 32 | re = regexp.MustCompile("^[a-zA-Z0-9]+([a-zA-Z0-9-]*[a-zA-Z0-9]*)?$") 33 | // Default server name 34 | Name = "go.micro.web" 35 | // Default address to bind to 36 | Address = ":8082" 37 | // The namespace to serve 38 | // Example: 39 | // Namespace + /[Service]/foo/bar 40 | // Host: Namespace.Service Endpoint: /foo/bar 41 | Namespace = "go.micro.web" 42 | // Base path sent to web service. 43 | // This is stripped from the request path 44 | // Allows the web service to define absolute paths 45 | BasePathHeader = "X-Micro-Web-Base-Path" 46 | statsURL string 47 | ) 48 | 49 | type srv struct { 50 | *mux.Router 51 | } 52 | 53 | func (s *srv) proxy() http.Handler { 54 | sel := selector.NewSelector( 55 | selector.Registry((*cmd.DefaultOptions().Registry)), 56 | ) 57 | 58 | director := func(r *http.Request) { 59 | kill := func() { 60 | r.URL.Host = "" 61 | r.URL.Path = "" 62 | r.URL.Scheme = "" 63 | r.Host = "" 64 | r.RequestURI = "" 65 | } 66 | 67 | parts := strings.Split(r.URL.Path, "/") 68 | if len(parts) < 2 { 69 | kill() 70 | return 71 | } 72 | if !re.MatchString(parts[1]) { 73 | kill() 74 | return 75 | } 76 | next, err := sel.Select(Namespace + "." + parts[1]) 77 | if err != nil { 78 | kill() 79 | return 80 | } 81 | 82 | s, err := next() 83 | if err != nil { 84 | kill() 85 | return 86 | } 87 | 88 | r.Header.Set(BasePathHeader, "/"+parts[1]) 89 | r.URL.Host = fmt.Sprintf("%s:%d", s.Address, s.Port) 90 | r.URL.Path = "/" + strings.Join(parts[2:], "/") 91 | r.URL.Scheme = "http" 92 | r.Host = r.URL.Host 93 | } 94 | 95 | return &proxy{ 96 | Default: &httputil.ReverseProxy{Director: director}, 97 | Director: director, 98 | } 99 | } 100 | 101 | func format(v *registry.Value) string { 102 | if v == nil || len(v.Values) == 0 { 103 | return "{}" 104 | } 105 | var f []string 106 | for _, k := range v.Values { 107 | f = append(f, formatEndpoint(k, 0)) 108 | } 109 | return fmt.Sprintf("{\n%s}", strings.Join(f, "")) 110 | } 111 | 112 | func formatEndpoint(v *registry.Value, r int) string { 113 | // default format is tabbed plus the value plus new line 114 | fparts := []string{"", "%s %s", "\n"} 115 | for i := 0; i < r+1; i++ { 116 | fparts[0] += "\t" 117 | } 118 | // its just a primitive of sorts so return 119 | if len(v.Values) == 0 { 120 | return fmt.Sprintf(strings.Join(fparts, ""), snaker.CamelToSnake(v.Name), v.Type) 121 | } 122 | 123 | // this thing has more things, it's complex 124 | fparts[1] += " {" 125 | 126 | vals := []interface{}{snaker.CamelToSnake(v.Name), v.Type} 127 | 128 | for _, val := range v.Values { 129 | fparts = append(fparts, "%s") 130 | vals = append(vals, formatEndpoint(val, r+1)) 131 | } 132 | 133 | // at the end 134 | l := len(fparts) - 1 135 | for i := 0; i < r+1; i++ { 136 | fparts[l] += "\t" 137 | } 138 | fparts = append(fparts, "}\n") 139 | 140 | return fmt.Sprintf(strings.Join(fparts, ""), vals...) 141 | } 142 | 143 | func faviconHandler(w http.ResponseWriter, r *http.Request) { 144 | return 145 | } 146 | 147 | func cliHandler(w http.ResponseWriter, r *http.Request) { 148 | render(w, r, cliTemplate, nil) 149 | } 150 | 151 | func indexHandler(w http.ResponseWriter, r *http.Request) { 152 | services, err := (*cmd.DefaultOptions().Registry).ListServices() 153 | if err != nil { 154 | http.Error(w, "Error occurred:"+err.Error(), 500) 155 | return 156 | } 157 | 158 | var webServices []string 159 | for _, s := range services { 160 | if strings.Index(s.Name, Namespace) == 0 && len(strings.TrimPrefix(s.Name, Namespace)) > 0 { 161 | webServices = append(webServices, strings.Replace(s.Name, Namespace+".", "", 1)) 162 | } 163 | } 164 | 165 | sort.Strings(webServices) 166 | 167 | type templateData struct { 168 | HasWebServices bool 169 | WebServices []string 170 | } 171 | 172 | data := templateData{len(webServices) > 0, webServices} 173 | render(w, r, indexTemplate, data) 174 | } 175 | 176 | func registryHandler(w http.ResponseWriter, r *http.Request) { 177 | r.ParseForm() 178 | svc := r.Form.Get("service") 179 | 180 | if len(svc) > 0 { 181 | s, err := (*cmd.DefaultOptions().Registry).GetService(svc) 182 | if err != nil { 183 | http.Error(w, "Error occurred:"+err.Error(), 500) 184 | return 185 | } 186 | 187 | if len(s) == 0 { 188 | http.Error(w, "Not found", 404) 189 | return 190 | } 191 | 192 | if r.Header.Get("Content-Type") == "application/json" { 193 | b, err := json.Marshal(map[string]interface{}{ 194 | "services": s, 195 | }) 196 | if err != nil { 197 | http.Error(w, "Error occurred:"+err.Error(), 500) 198 | return 199 | } 200 | w.Header().Set("Content-Type", "application/json") 201 | w.Write(b) 202 | return 203 | } 204 | 205 | render(w, r, serviceTemplate, s) 206 | return 207 | } 208 | 209 | services, err := (*cmd.DefaultOptions().Registry).ListServices() 210 | if err != nil { 211 | http.Error(w, "Error occurred:"+err.Error(), 500) 212 | return 213 | } 214 | 215 | sort.Sort(sortedServices{services}) 216 | 217 | if r.Header.Get("Content-Type") == "application/json" { 218 | b, err := json.Marshal(map[string]interface{}{ 219 | "services": services, 220 | }) 221 | if err != nil { 222 | http.Error(w, "Error occurred:"+err.Error(), 500) 223 | return 224 | } 225 | w.Header().Set("Content-Type", "application/json") 226 | w.Write(b) 227 | return 228 | } 229 | 230 | render(w, r, registryTemplate, services) 231 | } 232 | 233 | func callHandler(w http.ResponseWriter, r *http.Request) { 234 | services, err := (*cmd.DefaultOptions().Registry).ListServices() 235 | if err != nil { 236 | http.Error(w, "Error occurred:"+err.Error(), 500) 237 | return 238 | } 239 | 240 | sort.Sort(sortedServices{services}) 241 | 242 | serviceMap := make(map[string][]*registry.Endpoint) 243 | for _, service := range services { 244 | s, err := (*cmd.DefaultOptions().Registry).GetService(service.Name) 245 | if err != nil { 246 | continue 247 | } 248 | if len(s) == 0 { 249 | continue 250 | } 251 | serviceMap[service.Name] = s[0].Endpoints 252 | } 253 | 254 | if r.Header.Get("Content-Type") == "application/json" { 255 | b, err := json.Marshal(map[string]interface{}{ 256 | "services": services, 257 | }) 258 | if err != nil { 259 | http.Error(w, "Error occurred:"+err.Error(), 500) 260 | return 261 | } 262 | w.Header().Set("Content-Type", "application/json") 263 | w.Write(b) 264 | return 265 | } 266 | 267 | render(w, r, callTemplate, serviceMap) 268 | } 269 | 270 | func render(w http.ResponseWriter, r *http.Request, tmpl string, data interface{}) { 271 | t, err := template.New("template").Funcs(template.FuncMap{ 272 | "format": format, 273 | }).Parse(layoutTemplate) 274 | if err != nil { 275 | http.Error(w, "Error occurred:"+err.Error(), 500) 276 | return 277 | } 278 | t, err = t.Parse(tmpl) 279 | if err != nil { 280 | http.Error(w, "Error occurred:"+err.Error(), 500) 281 | return 282 | } 283 | 284 | if err := t.ExecuteTemplate(w, "layout", map[string]interface{}{ 285 | "StatsURL": statsURL, 286 | "Results": data, 287 | }); err != nil { 288 | http.Error(w, "Error occurred:"+err.Error(), 500) 289 | } 290 | } 291 | 292 | func run(ctx *cli.Context, srvOpts ...micro.Option) { 293 | if len(ctx.GlobalString("server_name")) > 0 { 294 | Name = ctx.GlobalString("server_name") 295 | } 296 | if len(ctx.String("address")) > 0 { 297 | Address = ctx.String("address") 298 | } 299 | if len(ctx.String("namespace")) > 0 { 300 | Namespace = ctx.String("namespace") 301 | } 302 | 303 | // Init plugins 304 | for _, p := range Plugins() { 305 | p.Init(ctx) 306 | } 307 | 308 | var h http.Handler 309 | r := mux.NewRouter() 310 | s := &srv{r} 311 | h = s 312 | 313 | if ctx.GlobalBool("enable_stats") { 314 | statsURL = "/stats" 315 | st := stats.New() 316 | s.HandleFunc("/stats", st.StatsHandler) 317 | h = st.ServeHTTP(s) 318 | st.Start() 319 | defer st.Stop() 320 | } 321 | 322 | s.HandleFunc("/registry", registryHandler) 323 | s.HandleFunc("/rpc", handler.RPC) 324 | s.HandleFunc("/cli", cliHandler) 325 | s.HandleFunc("/call", callHandler) 326 | s.HandleFunc("/favicon.ico", faviconHandler) 327 | s.PathPrefix("/{service:[a-zA-Z0-9]+}").Handler(s.proxy()) 328 | s.HandleFunc("/", indexHandler) 329 | 330 | var opts []server.Option 331 | 332 | if ctx.GlobalBool("enable_acme") { 333 | hosts := helper.ACMEHosts(ctx) 334 | opts = append(opts, server.EnableACME(true)) 335 | opts = append(opts, server.ACMEHosts(hosts...)) 336 | } else if ctx.GlobalBool("enable_tls") { 337 | config, err := helper.TLSConfig(ctx) 338 | if err != nil { 339 | fmt.Println(err.Error()) 340 | return 341 | } 342 | 343 | opts = append(opts, server.EnableTLS(true)) 344 | opts = append(opts, server.TLSConfig(config)) 345 | } 346 | 347 | // reverse wrap handler 348 | plugins := append(Plugins(), plugin.Plugins()...) 349 | for i := len(plugins); i > 0; i-- { 350 | h = plugins[i-1].Handler()(h) 351 | } 352 | 353 | srv := httpapi.NewServer(Address) 354 | srv.Init(opts...) 355 | srv.Handle("/", h) 356 | 357 | // service opts 358 | srvOpts = append(srvOpts, micro.Name(Name)) 359 | if i := time.Duration(ctx.GlobalInt("register_ttl")); i > 0 { 360 | srvOpts = append(srvOpts, micro.RegisterTTL(i*time.Second)) 361 | } 362 | if i := time.Duration(ctx.GlobalInt("register_interval")); i > 0 { 363 | srvOpts = append(srvOpts, micro.RegisterInterval(i*time.Second)) 364 | } 365 | 366 | // Initialise Server 367 | service := micro.NewService(srvOpts...) 368 | 369 | if err := srv.Start(); err != nil { 370 | log.Fatal(err) 371 | } 372 | 373 | // Run server 374 | if err := service.Run(); err != nil { 375 | log.Fatal(err) 376 | } 377 | 378 | if err := srv.Stop(); err != nil { 379 | log.Fatal(err) 380 | } 381 | } 382 | 383 | func Commands(options ...micro.Option) []cli.Command { 384 | command := cli.Command{ 385 | Name: "web", 386 | Usage: "Run the web dashboard", 387 | Action: func(c *cli.Context) { 388 | run(c, options...) 389 | }, 390 | Flags: []cli.Flag{ 391 | cli.StringFlag{ 392 | Name: "address", 393 | Usage: "Set the web UI address e.g 0.0.0.0:8082", 394 | EnvVar: "MICRO_WEB_ADDRESS", 395 | }, 396 | cli.StringFlag{ 397 | Name: "namespace", 398 | Usage: "Set the namespace used by the Web proxy e.g. com.example.web", 399 | EnvVar: "MICRO_WEB_NAMESPACE", 400 | }, 401 | }, 402 | } 403 | 404 | for _, p := range Plugins() { 405 | if cmds := p.Commands(); len(cmds) > 0 { 406 | command.Subcommands = append(command.Subcommands, cmds...) 407 | } 408 | 409 | if flags := p.Flags(); len(flags) > 0 { 410 | command.Flags = append(command.Flags, flags...) 411 | } 412 | } 413 | 414 | return []cli.Command{command} 415 | } 416 | -------------------------------------------------------------------------------- /bot/bot.go: -------------------------------------------------------------------------------- 1 | // Package bot is a Hubot style bot that sits a microservice environment 2 | package bot 3 | 4 | import ( 5 | "context" 6 | "fmt" 7 | "os" 8 | "regexp" 9 | "sort" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | "github.com/micro/cli" 15 | "github.com/micro/go-micro" 16 | 17 | "github.com/micro/go-bot/command" 18 | "github.com/micro/go-bot/input" 19 | _ "github.com/micro/go-bot/input/hipchat" 20 | _ "github.com/micro/go-bot/input/slack" 21 | "github.com/micro/go-log" 22 | botc "github.com/micro/micro/internal/command/bot" 23 | 24 | proto "github.com/micro/go-bot/proto" 25 | ) 26 | 27 | type bot struct { 28 | exit chan bool 29 | ctx *cli.Context 30 | service micro.Service 31 | 32 | sync.RWMutex 33 | inputs map[string]input.Input 34 | commands map[string]command.Command 35 | services map[string]string 36 | } 37 | 38 | var ( 39 | // Default server name 40 | Name = "go.micro.bot" 41 | // Namespace for commands 42 | Namespace = "go.micro.bot" 43 | // map pattern:command 44 | commands = map[string]func(*cli.Context) command.Command{ 45 | "^echo ": botc.Echo, 46 | "^time$": botc.Time, 47 | "^hello$": botc.Hello, 48 | "^ping$": botc.Ping, 49 | "^list ": botc.List, 50 | "^get ": botc.Get, 51 | "^health ": botc.Health, 52 | "^call ": botc.Call, 53 | "^register ": botc.Register, 54 | "^deregister ": botc.Deregister, 55 | "^(the )?three laws( of robotics)?$": botc.ThreeLaws, 56 | } 57 | ) 58 | 59 | func help(commands map[string]command.Command, serviceCommands []string) command.Command { 60 | usage := "help" 61 | desc := "Displays help for all known commands" 62 | 63 | var cmds []command.Command 64 | 65 | for _, cmd := range commands { 66 | cmds = append(cmds, cmd) 67 | } 68 | 69 | sort.Sort(sortedCommands{cmds}) 70 | 71 | return command.NewCommand("help", usage, desc, func(args ...string) ([]byte, error) { 72 | response := []string{"\n"} 73 | for _, cmd := range cmds { 74 | response = append(response, fmt.Sprintf("%s - %s", cmd.Usage(), cmd.Description())) 75 | } 76 | response = append(response, serviceCommands...) 77 | return []byte(strings.Join(response, "\n")), nil 78 | }) 79 | } 80 | 81 | func newBot(ctx *cli.Context, inputs map[string]input.Input, commands map[string]command.Command, service micro.Service) *bot { 82 | commands["^help$"] = help(commands, nil) 83 | 84 | return &bot{ 85 | ctx: ctx, 86 | exit: make(chan bool), 87 | service: service, 88 | commands: commands, 89 | inputs: inputs, 90 | services: make(map[string]string), 91 | } 92 | } 93 | 94 | func (b *bot) loop(io input.Input) { 95 | log.Logf("[bot][loop] starting %s", io.String()) 96 | 97 | for { 98 | select { 99 | case <-b.exit: 100 | log.Logf("[bot][loop] exiting %s", io.String()) 101 | return 102 | default: 103 | if err := b.run(io); err != nil { 104 | log.Logf("[bot][loop] error %v", err) 105 | time.Sleep(time.Second) 106 | } 107 | } 108 | } 109 | } 110 | 111 | func (b *bot) process(c input.Conn, ev input.Event) error { 112 | args := strings.Split(string(ev.Data), " ") 113 | if len(args) == 0 { 114 | return nil 115 | } 116 | 117 | b.RLock() 118 | defer b.RUnlock() 119 | 120 | // try built in command 121 | for pattern, cmd := range b.commands { 122 | // skip if it doesn't match 123 | if m, err := regexp.Match(pattern, ev.Data); err != nil || !m { 124 | continue 125 | } 126 | 127 | // matched, exec command 128 | rsp, err := cmd.Exec(args...) 129 | if err != nil { 130 | rsp = []byte("error executing cmd: " + err.Error()) 131 | } 132 | 133 | // send response 134 | return c.Send(&input.Event{ 135 | Meta: ev.Meta, 136 | From: ev.To, 137 | To: ev.From, 138 | Type: input.TextEvent, 139 | Data: rsp, 140 | }) 141 | } 142 | 143 | // no built in match 144 | // try service commands 145 | service := Namespace + "." + args[0] 146 | 147 | // is there a service for the command? 148 | if _, ok := b.services[service]; !ok { 149 | return nil 150 | } 151 | 152 | // make service request 153 | req := b.service.Client().NewRequest(service, "Command.Exec", &proto.ExecRequest{ 154 | Args: args, 155 | }) 156 | rsp := &proto.ExecResponse{} 157 | 158 | var response []byte 159 | 160 | // call service 161 | if err := b.service.Client().Call(context.Background(), req, rsp); err != nil { 162 | response = []byte("error executing cmd: " + err.Error()) 163 | } else if len(rsp.Error) > 0 { 164 | response = []byte("error executing cmd: " + rsp.Error) 165 | } else { 166 | response = rsp.Result 167 | } 168 | 169 | // send response 170 | return c.Send(&input.Event{ 171 | Meta: ev.Meta, 172 | From: ev.To, 173 | To: ev.From, 174 | Type: input.TextEvent, 175 | Data: response, 176 | }) 177 | } 178 | 179 | func (b *bot) run(io input.Input) error { 180 | log.Logf("[bot][loop] connecting to %s", io.String()) 181 | 182 | c, err := io.Stream() 183 | if err != nil { 184 | return err 185 | } 186 | 187 | for { 188 | select { 189 | case <-b.exit: 190 | log.Logf("[bot][loop] closing %s", io.String()) 191 | return c.Close() 192 | default: 193 | var recvEv input.Event 194 | // receive input 195 | if err := c.Recv(&recvEv); err != nil { 196 | return err 197 | } 198 | 199 | // only process TextEvent 200 | if recvEv.Type != input.TextEvent { 201 | continue 202 | } 203 | 204 | if len(recvEv.Data) == 0 { 205 | continue 206 | } 207 | 208 | if err := b.process(c, recvEv); err != nil { 209 | return err 210 | } 211 | } 212 | } 213 | } 214 | 215 | func (b *bot) start() error { 216 | log.Log("[bot] starting") 217 | 218 | // Start inputs 219 | for _, io := range b.inputs { 220 | log.Logf("[bot] starting input %s", io.String()) 221 | 222 | if err := io.Init(b.ctx); err != nil { 223 | return err 224 | } 225 | 226 | if err := io.Start(); err != nil { 227 | return err 228 | } 229 | 230 | go b.loop(io) 231 | } 232 | 233 | // start watcher 234 | go b.watch() 235 | 236 | return nil 237 | } 238 | 239 | func (b *bot) stop() error { 240 | log.Log("[bot] stopping") 241 | close(b.exit) 242 | 243 | // Stop inputs 244 | for _, io := range b.inputs { 245 | log.Logf("[bot] stopping input %s", io.String()) 246 | if err := io.Stop(); err != nil { 247 | log.Logf("[bot] %v", err) 248 | } 249 | } 250 | 251 | return nil 252 | } 253 | 254 | func (b *bot) watch() { 255 | commands := map[string]command.Command{} 256 | services := map[string]string{} 257 | 258 | // copy commands 259 | b.RLock() 260 | for k, v := range b.commands { 261 | commands[k] = v 262 | } 263 | b.RUnlock() 264 | 265 | // getHelp retries usage and description from bot service commands 266 | getHelp := func(service string) (string, error) { 267 | // is within namespace? 268 | if !strings.HasPrefix(service, Namespace) { 269 | return "", fmt.Errorf("%s not within namespace", service) 270 | } 271 | 272 | if p := strings.TrimPrefix(service, Namespace); len(p) == 0 { 273 | return "", fmt.Errorf("%s not a service", service) 274 | } 275 | 276 | // get command help 277 | req := b.service.Client().NewRequest(service, "Command.Help", &proto.HelpRequest{}) 278 | rsp := &proto.HelpResponse{} 279 | 280 | err := b.service.Client().Call(context.Background(), req, rsp) 281 | if err != nil { 282 | return "", err 283 | } 284 | 285 | return fmt.Sprintf("%s - %s", rsp.Usage, rsp.Description), nil 286 | } 287 | 288 | serviceList, err := b.service.Client().Options().Registry.ListServices() 289 | if err != nil { 290 | // log error? 291 | return 292 | } 293 | 294 | var serviceCommands []string 295 | 296 | // create service commands 297 | for _, service := range serviceList { 298 | h, err := getHelp(service.Name) 299 | if err != nil { 300 | continue 301 | } 302 | services[service.Name] = h 303 | serviceCommands = append(serviceCommands, h) 304 | } 305 | 306 | b.Lock() 307 | b.commands["^help$"] = help(commands, serviceCommands) 308 | b.services = services 309 | b.Unlock() 310 | 311 | w, err := b.service.Client().Options().Registry.Watch() 312 | if err != nil { 313 | // log error? 314 | return 315 | } 316 | 317 | go func() { 318 | <-b.exit 319 | w.Stop() 320 | }() 321 | 322 | // watch for changes to services 323 | for { 324 | res, err := w.Next() 325 | if err != nil { 326 | return 327 | } 328 | 329 | if res.Action == "delete" { 330 | delete(services, res.Service.Name) 331 | } else { 332 | h, err := getHelp(res.Service.Name) 333 | if err != nil { 334 | continue 335 | } 336 | services[res.Service.Name] = h 337 | } 338 | 339 | var serviceCommands []string 340 | for _, v := range services { 341 | serviceCommands = append(serviceCommands, v) 342 | } 343 | 344 | b.Lock() 345 | b.commands["^help$"] = help(commands, serviceCommands) 346 | b.services = services 347 | b.Unlock() 348 | } 349 | } 350 | 351 | func run(ctx *cli.Context) { 352 | // Init plugins 353 | for _, p := range Plugins() { 354 | p.Init(ctx) 355 | } 356 | 357 | if len(ctx.GlobalString("server_name")) > 0 { 358 | Name = ctx.GlobalString("server_name") 359 | } 360 | 361 | if len(ctx.String("namespace")) > 0 { 362 | Namespace = ctx.String("namespace") 363 | } 364 | 365 | // Parse flags 366 | if len(ctx.String("inputs")) == 0 { 367 | log.Fatal("[bot] no inputs specified") 368 | } 369 | 370 | inputs := strings.Split(ctx.String("inputs"), ",") 371 | if len(inputs) == 0 { 372 | log.Fatal("[bot] no inputs specified") 373 | } 374 | 375 | ios := make(map[string]input.Input) 376 | cmds := make(map[string]command.Command) 377 | 378 | // create built in commands 379 | for pattern, cmd := range commands { 380 | cmds[pattern] = cmd(ctx) 381 | } 382 | 383 | // take other commands 384 | for pattern, cmd := range command.Commands { 385 | if c, ok := cmds[pattern]; ok { 386 | log.Logf("[bot] command %s already registered for pattern %s\n", c.String(), pattern) 387 | continue 388 | } 389 | // register command 390 | cmds[pattern] = cmd 391 | } 392 | 393 | // Parse inputs 394 | for _, io := range inputs { 395 | i, ok := input.Inputs[io] 396 | if !ok { 397 | log.Logf("[bot] input %s not found\n", i) 398 | os.Exit(1) 399 | } 400 | ios[io] = i 401 | } 402 | 403 | // setup service 404 | service := micro.NewService( 405 | micro.Name(Name), 406 | micro.RegisterTTL( 407 | time.Duration(ctx.GlobalInt("register_ttl"))*time.Second, 408 | ), 409 | micro.RegisterInterval( 410 | time.Duration(ctx.GlobalInt("register_interval"))*time.Second, 411 | ), 412 | ) 413 | 414 | // Start bot 415 | b := newBot(ctx, ios, cmds, service) 416 | 417 | if err := b.start(); err != nil { 418 | log.Logf("error starting bot %v", err) 419 | os.Exit(1) 420 | } 421 | 422 | // Run server 423 | if err := service.Run(); err != nil { 424 | log.Fatal(err) 425 | } 426 | 427 | // Stop bot 428 | if err := b.stop(); err != nil { 429 | log.Logf("error stopping bot %v", err) 430 | } 431 | } 432 | 433 | func Commands() []cli.Command { 434 | flags := []cli.Flag{ 435 | cli.StringFlag{ 436 | Name: "inputs", 437 | Usage: "Inputs to load on startup", 438 | }, 439 | cli.StringFlag{ 440 | Name: "namespace", 441 | Usage: "Set the namespace used by the bot to find commands e.g. com.example.bot", 442 | EnvVar: "MICRO_BOT_NAMESPACE", 443 | }, 444 | } 445 | 446 | // setup input flags 447 | for _, input := range input.Inputs { 448 | flags = append(flags, input.Flags()...) 449 | } 450 | 451 | command := cli.Command{ 452 | Name: "bot", 453 | Usage: "Run the chatops bot", 454 | Flags: flags, 455 | Action: run, 456 | } 457 | 458 | for _, p := range Plugins() { 459 | if cmds := p.Commands(); len(cmds) > 0 { 460 | command.Subcommands = append(command.Subcommands, cmds...) 461 | } 462 | 463 | if flags := p.Flags(); len(flags) > 0 { 464 | command.Flags = append(command.Flags, flags...) 465 | } 466 | } 467 | 468 | return []cli.Command{command} 469 | } 470 | -------------------------------------------------------------------------------- /internal/command/cli/command.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "net/http" 10 | "sort" 11 | "strings" 12 | "time" 13 | 14 | "github.com/micro/cli" 15 | "github.com/micro/go-micro/client" 16 | "github.com/micro/go-micro/cmd" 17 | "github.com/micro/go-micro/registry" 18 | 19 | proto "github.com/micro/go-micro/server/debug/proto" 20 | 21 | "github.com/serenize/snaker" 22 | ) 23 | 24 | func formatEndpoint(v *registry.Value, r int) string { 25 | // default format is tabbed plus the value plus new line 26 | fparts := []string{"", "%s %s", "\n"} 27 | for i := 0; i < r+1; i++ { 28 | fparts[0] += "\t" 29 | } 30 | // its just a primitive of sorts so return 31 | if len(v.Values) == 0 { 32 | return fmt.Sprintf(strings.Join(fparts, ""), snaker.CamelToSnake(v.Name), v.Type) 33 | } 34 | 35 | // this thing has more things, it's complex 36 | fparts[1] += " {" 37 | 38 | vals := []interface{}{snaker.CamelToSnake(v.Name), v.Type} 39 | 40 | for _, val := range v.Values { 41 | fparts = append(fparts, "%s") 42 | vals = append(vals, formatEndpoint(val, r+1)) 43 | } 44 | 45 | // at the end 46 | l := len(fparts) - 1 47 | for i := 0; i < r+1; i++ { 48 | fparts[l] += "\t" 49 | } 50 | fparts = append(fparts, "}\n") 51 | 52 | return fmt.Sprintf(strings.Join(fparts, ""), vals...) 53 | } 54 | 55 | func del(url string, b []byte, v interface{}) error { 56 | if !strings.HasPrefix(url, "http") && !strings.HasPrefix(url, "https") { 57 | url = "http://" + url 58 | } 59 | 60 | buf := bytes.NewBuffer(b) 61 | defer buf.Reset() 62 | 63 | req, err := http.NewRequest("DELETE", url, buf) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | rsp, err := http.DefaultClient.Do(req) 69 | if err != nil { 70 | return err 71 | } 72 | defer rsp.Body.Close() 73 | 74 | if v == nil { 75 | return nil 76 | } 77 | 78 | d := json.NewDecoder(rsp.Body) 79 | d.UseNumber() 80 | return d.Decode(v) 81 | } 82 | 83 | func get(url string, v interface{}) error { 84 | if !strings.HasPrefix(url, "http") && !strings.HasPrefix(url, "https") { 85 | url = "http://" + url 86 | } 87 | 88 | rsp, err := http.Get(url) 89 | if err != nil { 90 | return err 91 | } 92 | defer rsp.Body.Close() 93 | 94 | d := json.NewDecoder(rsp.Body) 95 | d.UseNumber() 96 | return d.Decode(v) 97 | } 98 | 99 | func post(url string, b []byte, v interface{}) error { 100 | if !strings.HasPrefix(url, "http") && !strings.HasPrefix(url, "https") { 101 | url = "http://" + url 102 | } 103 | 104 | buf := bytes.NewBuffer(b) 105 | defer buf.Reset() 106 | 107 | rsp, err := http.Post(url, "application/json", buf) 108 | if err != nil { 109 | return err 110 | } 111 | defer rsp.Body.Close() 112 | 113 | if v == nil { 114 | return nil 115 | } 116 | 117 | d := json.NewDecoder(rsp.Body) 118 | d.UseNumber() 119 | return d.Decode(v) 120 | } 121 | 122 | func RegisterService(c *cli.Context, args []string) ([]byte, error) { 123 | if len(args) == 0 { 124 | return nil, errors.New("require service definition") 125 | } 126 | 127 | req := strings.Join(args, " ") 128 | 129 | if p := c.GlobalString("proxy_address"); len(p) > 0 { 130 | if err := post(p+"/registry", []byte(req), nil); err != nil { 131 | return nil, err 132 | } 133 | return []byte("ok"), nil 134 | } 135 | 136 | var service *registry.Service 137 | 138 | d := json.NewDecoder(strings.NewReader(req)) 139 | d.UseNumber() 140 | 141 | if err := d.Decode(&service); err != nil { 142 | return nil, err 143 | } 144 | 145 | if err := (*cmd.DefaultOptions().Registry).Register(service); err != nil { 146 | return nil, err 147 | } 148 | 149 | return []byte("ok"), nil 150 | } 151 | 152 | func DeregisterService(c *cli.Context, args []string) ([]byte, error) { 153 | if len(args) == 0 { 154 | return nil, errors.New("require service definition") 155 | } 156 | 157 | req := strings.Join(args, " ") 158 | 159 | if p := c.GlobalString("proxy_address"); len(p) > 0 { 160 | if err := del(p+"/registry", []byte(req), nil); err != nil { 161 | return nil, err 162 | } 163 | return []byte("ok"), nil 164 | } 165 | 166 | var service *registry.Service 167 | 168 | d := json.NewDecoder(strings.NewReader(req)) 169 | d.UseNumber() 170 | 171 | if err := d.Decode(&service); err != nil { 172 | return nil, err 173 | } 174 | 175 | if err := (*cmd.DefaultOptions().Registry).Deregister(service); err != nil { 176 | return nil, err 177 | } 178 | 179 | return []byte("ok"), nil 180 | } 181 | 182 | func GetService(c *cli.Context, args []string) ([]byte, error) { 183 | if len(args) == 0 { 184 | return nil, errors.New("service required") 185 | } 186 | 187 | var output []string 188 | var service []*registry.Service 189 | var err error 190 | 191 | if p := c.GlobalString("proxy_address"); len(p) > 0 { 192 | if err := get(p+"/registry?service="+args[0], &service); err != nil { 193 | return nil, err 194 | } 195 | } else { 196 | service, err = (*cmd.DefaultOptions().Registry).GetService(args[0]) 197 | } 198 | 199 | if err != nil { 200 | return nil, err 201 | } 202 | 203 | if len(service) == 0 { 204 | return nil, errors.New("Service not found") 205 | } 206 | 207 | output = append(output, "service "+service[0].Name) 208 | 209 | for _, serv := range service { 210 | if len(serv.Version) > 0 { 211 | output = append(output, "\nversion "+serv.Version) 212 | } 213 | 214 | output = append(output, "\nID\tAddress\tPort\tMetadata") 215 | for _, node := range serv.Nodes { 216 | var meta []string 217 | for k, v := range node.Metadata { 218 | meta = append(meta, k+"="+v) 219 | } 220 | output = append(output, fmt.Sprintf("%s\t%s\t%d\t%s", node.Id, node.Address, node.Port, strings.Join(meta, ","))) 221 | } 222 | } 223 | 224 | for _, e := range service[0].Endpoints { 225 | var request, response string 226 | var meta []string 227 | for k, v := range e.Metadata { 228 | meta = append(meta, k+"="+v) 229 | } 230 | if e.Request != nil && len(e.Request.Values) > 0 { 231 | request = "{\n" 232 | for _, v := range e.Request.Values { 233 | request += formatEndpoint(v, 0) 234 | } 235 | request += "}" 236 | } else { 237 | request = "{}" 238 | } 239 | if e.Response != nil && len(e.Response.Values) > 0 { 240 | response = "{\n" 241 | for _, v := range e.Response.Values { 242 | response += formatEndpoint(v, 0) 243 | } 244 | response += "}" 245 | } else { 246 | response = "{}" 247 | } 248 | 249 | output = append(output, fmt.Sprintf("\nEndpoint: %s\nMetadata: %s\n", e.Name, strings.Join(meta, ","))) 250 | output = append(output, fmt.Sprintf("Request: %s\n\nResponse: %s\n", request, response)) 251 | } 252 | 253 | return []byte(strings.Join(output, "\n")), nil 254 | } 255 | 256 | func ListServices(c *cli.Context) ([]byte, error) { 257 | var rsp []*registry.Service 258 | var err error 259 | 260 | if p := c.GlobalString("proxy_address"); len(p) > 0 { 261 | if err := get(p+"/registry", &rsp); err != nil { 262 | return nil, err 263 | } 264 | } else { 265 | rsp, err = (*cmd.DefaultOptions().Registry).ListServices() 266 | if err != nil { 267 | return nil, err 268 | } 269 | } 270 | 271 | sort.Sort(sortedServices{rsp}) 272 | 273 | var services []string 274 | 275 | for _, service := range rsp { 276 | services = append(services, service.Name) 277 | } 278 | 279 | return []byte(strings.Join(services, "\n")), nil 280 | } 281 | 282 | func Publish(c *cli.Context, args []string) error { 283 | if len(args) < 2 { 284 | return errors.New("require topic and message") 285 | } 286 | defer func() { 287 | time.Sleep(time.Millisecond * 100) 288 | }() 289 | topic := args[0] 290 | message := args[1] 291 | 292 | cl := *cmd.DefaultOptions().Client 293 | ct := func(o *client.MessageOptions) { 294 | o.ContentType = "application/json" 295 | } 296 | 297 | d := json.NewDecoder(strings.NewReader(message)) 298 | d.UseNumber() 299 | 300 | var msg map[string]interface{} 301 | if err := d.Decode(&msg); err != nil { 302 | return err 303 | } 304 | 305 | m := cl.NewMessage(topic, msg, ct) 306 | return cl.Publish(context.Background(), m) 307 | } 308 | 309 | func CallService(c *cli.Context, args []string) ([]byte, error) { 310 | if len(args) < 2 { 311 | return nil, errors.New("require service and endpoint") 312 | } 313 | 314 | var req, service, endpoint string 315 | service = args[0] 316 | endpoint = args[1] 317 | 318 | if len(args) > 2 { 319 | req = strings.Join(args[2:], " ") 320 | } 321 | 322 | // empty request 323 | if len(req) == 0 { 324 | req = `{}` 325 | } 326 | 327 | var request map[string]interface{} 328 | var response json.RawMessage 329 | 330 | if p := c.GlobalString("proxy_address"); len(p) > 0 { 331 | request = map[string]interface{}{ 332 | "service": service, 333 | "endpoint": endpoint, 334 | "request": req, 335 | } 336 | 337 | b, err := json.Marshal(request) 338 | if err != nil { 339 | return nil, err 340 | } 341 | 342 | if err := post(p+"/rpc", b, &response); err != nil { 343 | return nil, err 344 | } 345 | 346 | } else { 347 | d := json.NewDecoder(strings.NewReader(req)) 348 | d.UseNumber() 349 | 350 | if err := d.Decode(&request); err != nil { 351 | return nil, err 352 | } 353 | 354 | creq := (*cmd.DefaultOptions().Client).NewRequest(service, endpoint, request, client.WithContentType("application/json")) 355 | err := (*cmd.DefaultOptions().Client).Call(context.Background(), creq, &response) 356 | if err != nil { 357 | return nil, fmt.Errorf("error calling %s.%s: %v", service, endpoint, err) 358 | } 359 | } 360 | 361 | var out bytes.Buffer 362 | defer out.Reset() 363 | if err := json.Indent(&out, response, "", "\t"); err != nil { 364 | return nil, err 365 | } 366 | return out.Bytes(), nil 367 | } 368 | 369 | func QueryHealth(c *cli.Context, args []string) ([]byte, error) { 370 | if len(args) == 0 { 371 | return nil, errors.New("require service name") 372 | } 373 | 374 | service, err := (*cmd.DefaultOptions().Registry).GetService(args[0]) 375 | if err != nil { 376 | return nil, err 377 | } 378 | 379 | if len(service) == 0 { 380 | return nil, errors.New("Service not found") 381 | } 382 | 383 | req := (*cmd.DefaultOptions().Client).NewRequest(service[0].Name, "Debug.Health", &proto.HealthRequest{}) 384 | 385 | var output []string 386 | 387 | // print things 388 | output = append(output, "service "+service[0].Name) 389 | 390 | for _, serv := range service { 391 | // print things 392 | output = append(output, "\nversion "+serv.Version) 393 | output = append(output, "\nnode\t\taddress:port\t\tstatus") 394 | 395 | // query health for every node 396 | for _, node := range serv.Nodes { 397 | address := node.Address 398 | if node.Port > 0 { 399 | address = fmt.Sprintf("%s:%d", address, node.Port) 400 | } 401 | rsp := &proto.HealthResponse{} 402 | 403 | var err error 404 | 405 | if p := c.GlobalString("proxy_address"); len(p) > 0 { 406 | // call using proxy 407 | request := map[string]interface{}{ 408 | "service": service[0].Name, 409 | "endpoint": "Debug.Health", 410 | "address": address, 411 | } 412 | 413 | b, err := json.Marshal(request) 414 | if err != nil { 415 | return nil, err 416 | } 417 | 418 | if err := post(p+"/rpc", b, &rsp); err != nil { 419 | return nil, err 420 | } 421 | } else { 422 | // call using client 423 | err = (*cmd.DefaultOptions().Client).Call( 424 | context.Background(), 425 | req, 426 | rsp, 427 | client.WithAddress(address), 428 | ) 429 | } 430 | 431 | var status string 432 | if err != nil { 433 | status = err.Error() 434 | } else { 435 | status = rsp.Status 436 | } 437 | output = append(output, fmt.Sprintf("%s\t\t%s:%d\t\t%s", node.Id, node.Address, node.Port, status)) 438 | } 439 | } 440 | 441 | return []byte(strings.Join(output, "\n")), nil 442 | } 443 | 444 | func QueryStats(c *cli.Context, args []string) ([]byte, error) { 445 | if len(args) == 0 { 446 | return nil, errors.New("require service name") 447 | } 448 | 449 | service, err := (*cmd.DefaultOptions().Registry).GetService(args[0]) 450 | if err != nil { 451 | return nil, err 452 | } 453 | 454 | if len(service) == 0 { 455 | return nil, errors.New("Service not found") 456 | } 457 | 458 | req := (*cmd.DefaultOptions().Client).NewRequest(service[0].Name, "Debug.Stats", &proto.StatsRequest{}) 459 | 460 | var output []string 461 | 462 | // print things 463 | output = append(output, "service "+service[0].Name) 464 | 465 | for _, serv := range service { 466 | // print things 467 | output = append(output, "\nversion "+serv.Version) 468 | output = append(output, "\nnode\t\taddress:port\t\tstarted\tuptime\tmemory\tthreads\tgc") 469 | 470 | // query health for every node 471 | for _, node := range serv.Nodes { 472 | address := node.Address 473 | if node.Port > 0 { 474 | address = fmt.Sprintf("%s:%d", address, node.Port) 475 | } 476 | rsp := &proto.StatsResponse{} 477 | 478 | var err error 479 | 480 | if p := c.GlobalString("proxy_address"); len(p) > 0 { 481 | // call using proxy 482 | request := map[string]interface{}{ 483 | "service": service[0].Name, 484 | "endpoint": "Debug.Stats", 485 | "address": address, 486 | } 487 | 488 | b, err := json.Marshal(request) 489 | if err != nil { 490 | return nil, err 491 | } 492 | 493 | if err := post(p+"/rpc", b, &rsp); err != nil { 494 | return nil, err 495 | } 496 | } else { 497 | // call using client 498 | err = (*cmd.DefaultOptions().Client).Call( 499 | context.Background(), 500 | req, 501 | rsp, 502 | client.WithAddress(address), 503 | ) 504 | } 505 | 506 | var started, uptime, memory, gc string 507 | if err == nil { 508 | started = time.Unix(int64(rsp.Started), 0).Format("Jan 2 15:04:05") 509 | uptime = fmt.Sprintf("%v", time.Duration(rsp.Uptime)*time.Second) 510 | memory = fmt.Sprintf("%.2fmb", float64(rsp.Memory)/(1024.0*1024.0)) 511 | gc = fmt.Sprintf("%v", time.Duration(rsp.Gc)) 512 | } 513 | 514 | line := fmt.Sprintf("%s\t\t%s:%d\t\t%s\t%s\t%s\t%d\t%s", 515 | node.Id, node.Address, node.Port, started, uptime, memory, rsp.Threads, gc) 516 | 517 | output = append(output, line) 518 | } 519 | } 520 | 521 | return []byte(strings.Join(output, "\n")), nil 522 | } 523 | -------------------------------------------------------------------------------- /web/templates.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | var ( 4 | layoutTemplate = ` 5 | {{define "layout"}} 6 | 7 | 8 | Micro Web 9 | 10 | 13 | {{ template "head" . }} 14 | 15 | 16 | 36 |
37 |
38 |
39 | {{ template "heading" . }} 40 | {{ template "content" . }} 41 |
42 |
43 |
44 | 45 | 46 | {{template "script" . }} 47 | 48 | 49 | {{end}} 50 | {{ define "style" }}{{end}} 51 | {{ define "head" }}{{end}} 52 | {{ define "script" }}{{end}} 53 | {{ define "title" }}{{end}} 54 | {{ define "heading" }}

 

{{end}} 55 | ` 56 | 57 | indexTemplate = ` 58 | {{define "heading"}}

{{end}} 59 | {{define "title"}}Web{{end}} 60 | {{define "content"}} 61 | {{if .Results.HasWebServices}} 62 |
63 | {{range .Results.WebServices}} 64 | {{.}} 65 | {{end}} 66 |
67 | {{else}} 68 | 71 | {{end}} 72 | {{end}} 73 | {{define "script"}} 74 | 86 | {{end}} 87 | ` 88 | callTemplate = ` 89 | {{define "title"}}Call{{end}} 90 | {{define "style"}} 91 | pre { 92 | word-wrap: break-word; 93 | } 94 | {{end}} 95 | {{define "content"}} 96 |
97 |
98 |
99 |
100 |
101 |
102 | 103 |
    104 | 110 |
111 |
112 |
113 | 114 |
    115 | 118 |
119 |
120 |
121 | 122 |
    123 | 124 |
125 |
126 |
127 | 128 | 129 |
130 |
131 | 132 |
133 |
134 |
135 |
136 |

Response

137 |
{}
138 |
139 |
140 |
141 |
142 | {{end}} 143 | {{define "script"}} 144 | 183 | 214 | {{end}} 215 | ` 216 | registryTemplate = ` 217 | {{define "heading"}}

{{end}} 218 | {{define "title"}}Registry{{end}} 219 | {{define "content"}} 220 |
221 | {{range .Results}} 222 | {{.Name}} 223 | {{end}} 224 |
225 | {{end}} 226 | {{define "script"}} 227 | 239 | {{end}} 240 | ` 241 | 242 | serviceTemplate = ` 243 | {{define "title"}}Service{{end}} 244 | {{define "heading"}}

{{with $svc := index .Results 0}}{{$svc.Name}}{{end}}

{{end}} 245 | {{define "content"}} 246 |
247 |

Nodes

248 | {{range .Results}} 249 |
Version {{.Version}}
250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | {{range .Nodes}} 259 | 260 | 261 | 262 | 263 | 264 | 265 | {{end}} 266 | 267 |
IdAddressPortMetadata
{{.Id}}{{.Address}}{{.Port}}{{ range $key, $value := .Metadata }}{{$key}}={{$value}} {{end}}
268 | {{end}} 269 | {{with $svc := index .Results 0}} 270 | {{if $svc.Endpoints}} 271 |

Endpoints

272 |
273 | {{end}} 274 | {{range $svc.Endpoints}} 275 |

{{.Name}}

276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 |
Metadata{{ range $key, $value := .Metadata }}{{$key}}={{$value}} {{end}}
Request
{{format .Request}}
Response
{{format .Response}}
292 | {{end}} 293 | {{end}} 294 | {{end}} 295 | 296 | ` 297 | 298 | cliTemplate = ` 299 | {{define "head"}} 300 | 301 | {{end}} 302 | {{define "title"}}CLI{{end}} 303 | {{define "content"}} 304 |
305 | {{end}} 306 | {{define "script"}} 307 | 308 | 496 | {{end}} 497 | ` 498 | ) 499 | --------------------------------------------------------------------------------