mirror of
				https://github.com/go-micro/go-micro.git
				synced 2025-10-30 23:27:41 +02:00 
			
		
		
		
	Further consolidate the libraries
This commit is contained in:
		
							
								
								
									
										7
									
								
								api/.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/.travis.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| language: go | ||||
| go: | ||||
| - 1.10.x | ||||
| - 1.11.x | ||||
| notifications: | ||||
|   slack: | ||||
|     secure: T84DYmc4NzjYLgsRw69ckiIh1iOXKZmuB3HeRNo68/6DOvHa7ypNrSzYIOVS1n9iZmmGRk2pnDiqSBV4kqdEuQstb+T4SiqOgb9FGd7PsT2xKPg4WRRNECogeyZhxBmYilAK6IfcFI+XuLUW/i3KLdZMFfKzDE/EVHBHGueyE3aVWruUv7pLfONlOoxK44ok+Ixa5RIiVTaGmyJ3N3fjg0Css3MeC4mmwld+3zlSadWBql+Vl/K1+M9Zu2XTfreaqfLYqA2lvPorO5d7D/ZEWtxSOnSihnrlj4U0JL9sduAmCF4JndKSVvdbo0tPvdy1ODbOFUP+HFe10q0eDt39Jn2prpLr/ATAyGPWdC0DppHIQ1QNLNsjmn+F6/FIcWnO3zbPLUbMdDp9n9xg4GD2qb7vhmepd63rCMQCG6z+3WIYYDY3cgGxKKUeG+dvD2LtrsxfiXbq+o7vocwrtyrHAGZk4WEM7ZwMIzN/71qard3eD4P1OmbJwZPOOXQius50tVjN/aK1YV7X/uh0JTtwYyiL5H07IiYCGAfSPShouJ8JQBvNmGRUJwXcWLANK+sCIF9do0KsqxIkeJzcctSl2e+DP/EVRZXmxs24nP2bAdIyG+JBCuhED3vKyfLS8mR7P/LtrZL1vrbEZWRt3s8q5vhvWbcpwxGqo25GS1NDIjM= | ||||
							
								
								
									
										18
									
								
								api/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								api/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| # Go API [](https://opensource.org/licenses/Apache-2.0) [](https://godoc.org/github.com/micro/go-micro/api) [](https://travis-ci.org/micro/go-micro/api) [](https://goreportcard.com/report/github.com/micro/go-micro/api) | ||||
|  | ||||
| Go API is a pluggable API framework driven by service discovery to help build powerful public API gateways. | ||||
|  | ||||
| ## Overview | ||||
|  | ||||
| The Go API library provides api gateway routing capabilities. A microservice architecture decouples application logic into  | ||||
| separate service. An api gateway provides a single entry point to consolidate these services into a unified api. The  | ||||
| Go API uses routes defined in service discovery metadata to generate routing rules and serve http requests. | ||||
|  | ||||
| <img src="https://micro.mu/docs/images/go-api.png?v=1" alt="Go API" /> | ||||
|  | ||||
| Go API is the basis for the [micro api](https://micro.mu/docs/api.html). | ||||
|  | ||||
| ## Getting Started | ||||
|  | ||||
| See the [docs](https://micro.mu/docs/go-api.html) to learn more | ||||
|  | ||||
							
								
								
									
										144
									
								
								api/api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								api/api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| // Endpoint is a mapping between an RPC method and HTTP endpoint | ||||
| type Endpoint struct { | ||||
| 	// RPC Method e.g. Greeter.Hello | ||||
| 	Name string | ||||
| 	// Description e.g what's this endpoint for | ||||
| 	Description string | ||||
| 	// API Handler e.g rpc, proxy | ||||
| 	Handler string | ||||
| 	// HTTP Host e.g example.com | ||||
| 	Host []string | ||||
| 	// HTTP Methods e.g GET, POST | ||||
| 	Method []string | ||||
| 	// HTTP Path e.g /greeter. Expect POSIX regex | ||||
| 	Path []string | ||||
| } | ||||
|  | ||||
| // Service represents an API service | ||||
| type Service struct { | ||||
| 	// Name of service | ||||
| 	Name string | ||||
| 	// The endpoint for this service | ||||
| 	Endpoint *Endpoint | ||||
| 	// Versions of this service | ||||
| 	Services []*registry.Service | ||||
| } | ||||
|  | ||||
| func strip(s string) string { | ||||
| 	return strings.TrimSpace(s) | ||||
| } | ||||
|  | ||||
| func slice(s string) []string { | ||||
| 	var sl []string | ||||
|  | ||||
| 	for _, p := range strings.Split(s, ",") { | ||||
| 		if str := strip(p); len(str) > 0 { | ||||
| 			sl = append(sl, strip(p)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return sl | ||||
| } | ||||
|  | ||||
| // Encode encodes an endpoint to endpoint metadata | ||||
| func Encode(e *Endpoint) map[string]string { | ||||
| 	if e == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	return map[string]string{ | ||||
| 		"endpoint":    e.Name, | ||||
| 		"description": e.Description, | ||||
| 		"method":      strings.Join(e.Method, ","), | ||||
| 		"path":        strings.Join(e.Path, ","), | ||||
| 		"host":        strings.Join(e.Host, ","), | ||||
| 		"handler":     e.Handler, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Decode decodes endpoint metadata into an endpoint | ||||
| func Decode(e map[string]string) *Endpoint { | ||||
| 	if e == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	return &Endpoint{ | ||||
| 		Name:        e["endpoint"], | ||||
| 		Description: e["description"], | ||||
| 		Method:      slice(e["method"]), | ||||
| 		Path:        slice(e["path"]), | ||||
| 		Host:        slice(e["host"]), | ||||
| 		Handler:     e["handler"], | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Validate validates an endpoint to guarantee it won't blow up when being served | ||||
| func Validate(e *Endpoint) error { | ||||
| 	if e == nil { | ||||
| 		return errors.New("endpoint is nil") | ||||
| 	} | ||||
|  | ||||
| 	if len(e.Name) == 0 { | ||||
| 		return errors.New("name required") | ||||
| 	} | ||||
|  | ||||
| 	for _, p := range e.Path { | ||||
| 		_, err := regexp.CompilePOSIX(p) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(e.Handler) == 0 { | ||||
| 		return errors.New("invalid handler") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| /* | ||||
| Design ideas | ||||
|  | ||||
| // Gateway is an api gateway interface | ||||
| type Gateway interface { | ||||
| 	// Register a http handler | ||||
| 	Handle(pattern string, http.Handler) | ||||
| 	// Register a route | ||||
| 	RegisterRoute(r Route) | ||||
| 	// Init initialises the command line. | ||||
| 	// It also parses further options. | ||||
| 	Init(...Option) error | ||||
| 	// Run the gateway | ||||
| 	Run() error | ||||
| } | ||||
|  | ||||
| // NewGateway returns a new api gateway | ||||
| func NewGateway() Gateway { | ||||
| 	return newGateway() | ||||
| } | ||||
| */ | ||||
|  | ||||
| // WithEndpoint returns a server.HandlerOption with endpoint metadata set | ||||
| // | ||||
| // Usage: | ||||
| // | ||||
| // 	proto.RegisterHandler(service.Server(), new(Handler), api.WithEndpoint( | ||||
| //		&api.Endpoint{ | ||||
| //			Name: "Greeter.Hello", | ||||
| //			Path: []string{"/greeter"}, | ||||
| //		}, | ||||
| //	)) | ||||
| func WithEndpoint(e *Endpoint) server.HandlerOption { | ||||
| 	return server.EndpointMetadata(e.Name, Encode(e)) | ||||
| } | ||||
							
								
								
									
										113
									
								
								api/api_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								api/api_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestEncoding(t *testing.T) { | ||||
| 	testData := []*Endpoint{ | ||||
| 		nil, | ||||
| 		{ | ||||
| 			Name:        "Foo.Bar", | ||||
| 			Description: "A test endpoint", | ||||
| 			Handler:     "meta", | ||||
| 			Host:        []string{"foo.com"}, | ||||
| 			Method:      []string{"GET"}, | ||||
| 			Path:        []string{"/test"}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	compare := func(expect, got []string) bool { | ||||
| 		// no data to compare, return true | ||||
| 		if len(expect) == 0 && len(got) == 0 { | ||||
| 			return true | ||||
| 		} | ||||
| 		// no data expected but got some return false | ||||
| 		if len(expect) == 0 && len(got) > 0 { | ||||
| 			return false | ||||
| 		} | ||||
|  | ||||
| 		// compare expected with what we got | ||||
| 		for _, e := range expect { | ||||
| 			var seen bool | ||||
| 			for _, g := range got { | ||||
| 				if e == g { | ||||
| 					seen = true | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 			if !seen { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// we're done, return true | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	for _, d := range testData { | ||||
| 		// encode | ||||
| 		e := Encode(d) | ||||
| 		// decode | ||||
| 		de := Decode(e) | ||||
|  | ||||
| 		// nil endpoint returns nil | ||||
| 		if d == nil { | ||||
| 			if e != nil { | ||||
| 				t.Fatalf("expected nil got %v", e) | ||||
| 			} | ||||
| 			if de != nil { | ||||
| 				t.Fatalf("expected nil got %v", de) | ||||
| 			} | ||||
|  | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// check encoded map | ||||
| 		name := e["endpoint"] | ||||
| 		desc := e["description"] | ||||
| 		method := strings.Split(e["method"], ",") | ||||
| 		path := strings.Split(e["path"], ",") | ||||
| 		host := strings.Split(e["host"], ",") | ||||
| 		handler := e["handler"] | ||||
|  | ||||
| 		if name != d.Name { | ||||
| 			t.Fatalf("expected %v got %v", d.Name, name) | ||||
| 		} | ||||
| 		if desc != d.Description { | ||||
| 			t.Fatalf("expected %v got %v", d.Description, desc) | ||||
| 		} | ||||
| 		if handler != d.Handler { | ||||
| 			t.Fatalf("expected %v got %v", d.Handler, handler) | ||||
| 		} | ||||
| 		if ok := compare(d.Method, method); !ok { | ||||
| 			t.Fatalf("expected %v got %v", d.Method, method) | ||||
| 		} | ||||
| 		if ok := compare(d.Path, path); !ok { | ||||
| 			t.Fatalf("expected %v got %v", d.Path, path) | ||||
| 		} | ||||
| 		if ok := compare(d.Host, host); !ok { | ||||
| 			t.Fatalf("expected %v got %v", d.Host, host) | ||||
| 		} | ||||
|  | ||||
| 		if de.Name != d.Name { | ||||
| 			t.Fatalf("expected %v got %v", d.Name, de.Name) | ||||
| 		} | ||||
| 		if de.Description != d.Description { | ||||
| 			t.Fatalf("expected %v got %v", d.Description, de.Description) | ||||
| 		} | ||||
| 		if de.Handler != d.Handler { | ||||
| 			t.Fatalf("expected %v got %v", d.Handler, de.Handler) | ||||
| 		} | ||||
| 		if ok := compare(d.Method, de.Method); !ok { | ||||
| 			t.Fatalf("expected %v got %v", d.Method, de.Method) | ||||
| 		} | ||||
| 		if ok := compare(d.Path, de.Path); !ok { | ||||
| 			t.Fatalf("expected %v got %v", d.Path, de.Path) | ||||
| 		} | ||||
| 		if ok := compare(d.Host, de.Host); !ok { | ||||
| 			t.Fatalf("expected %v got %v", d.Host, de.Host) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										117
									
								
								api/handler/api/api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								api/handler/api/api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| // Package api provides an http-rpc handler which provides the entire http request over rpc | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | ||||
| 	goapi "github.com/micro/go-micro/api" | ||||
| 	"github.com/micro/go-micro/api/handler" | ||||
| 	"github.com/micro/go-micro/client" | ||||
| 	"github.com/micro/go-micro/errors" | ||||
| 	"github.com/micro/go-micro/selector" | ||||
| 	"github.com/micro/go-micro/util/ctx" | ||||
| 	api "github.com/micro/micro/api/proto" | ||||
| ) | ||||
|  | ||||
| type apiHandler struct { | ||||
| 	opts handler.Options | ||||
| 	s    *goapi.Service | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	Handler = "api" | ||||
| ) | ||||
|  | ||||
| // API handler is the default handler which takes api.Request and returns api.Response | ||||
| func (a *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	request, err := requestToProto(r) | ||||
| 	if err != nil { | ||||
| 		er := errors.InternalServerError("go.micro.api", err.Error()) | ||||
| 		w.Header().Set("Content-Type", "application/json") | ||||
| 		w.WriteHeader(500) | ||||
| 		w.Write([]byte(er.Error())) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var service *goapi.Service | ||||
|  | ||||
| 	if a.s != nil { | ||||
| 		// we were given the service | ||||
| 		service = a.s | ||||
| 	} else if a.opts.Router != nil { | ||||
| 		// try get service from router | ||||
| 		s, err := a.opts.Router.Route(r) | ||||
| 		if err != nil { | ||||
| 			er := errors.InternalServerError("go.micro.api", err.Error()) | ||||
| 			w.Header().Set("Content-Type", "application/json") | ||||
| 			w.WriteHeader(500) | ||||
| 			w.Write([]byte(er.Error())) | ||||
| 			return | ||||
| 		} | ||||
| 		service = s | ||||
| 	} else { | ||||
| 		// we have no way of routing the request | ||||
| 		er := errors.InternalServerError("go.micro.api", "no route found") | ||||
| 		w.Header().Set("Content-Type", "application/json") | ||||
| 		w.WriteHeader(500) | ||||
| 		w.Write([]byte(er.Error())) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// create request and response | ||||
| 	c := a.opts.Service.Client() | ||||
| 	req := c.NewRequest(service.Name, service.Endpoint.Name, request) | ||||
| 	rsp := &api.Response{} | ||||
|  | ||||
| 	// create the context from headers | ||||
| 	cx := ctx.FromRequest(r) | ||||
| 	// create strategy | ||||
| 	so := selector.WithStrategy(strategy(service.Services)) | ||||
|  | ||||
| 	if err := c.Call(cx, req, rsp, client.WithSelectOption(so)); err != nil { | ||||
| 		w.Header().Set("Content-Type", "application/json") | ||||
| 		ce := errors.Parse(err.Error()) | ||||
| 		switch ce.Code { | ||||
| 		case 0: | ||||
| 			w.WriteHeader(500) | ||||
| 		default: | ||||
| 			w.WriteHeader(int(ce.Code)) | ||||
| 		} | ||||
| 		w.Write([]byte(ce.Error())) | ||||
| 		return | ||||
| 	} else if rsp.StatusCode == 0 { | ||||
| 		rsp.StatusCode = http.StatusOK | ||||
| 	} | ||||
|  | ||||
| 	for _, header := range rsp.GetHeader() { | ||||
| 		for _, val := range header.Values { | ||||
| 			w.Header().Add(header.Key, val) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(w.Header().Get("Content-Type")) == 0 { | ||||
| 		w.Header().Set("Content-Type", "application/json") | ||||
| 	} | ||||
|  | ||||
| 	w.WriteHeader(int(rsp.StatusCode)) | ||||
| 	w.Write([]byte(rsp.Body)) | ||||
| } | ||||
|  | ||||
| func (a *apiHandler) String() string { | ||||
| 	return "api" | ||||
| } | ||||
|  | ||||
| func NewHandler(opts ...handler.Option) handler.Handler { | ||||
| 	options := handler.NewOptions(opts...) | ||||
| 	return &apiHandler{ | ||||
| 		opts: options, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func WithService(s *goapi.Service, opts ...handler.Option) handler.Handler { | ||||
| 	options := handler.NewOptions(opts...) | ||||
| 	return &apiHandler{ | ||||
| 		opts: options, | ||||
| 		s:    s, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										107
									
								
								api/handler/api/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								api/handler/api/util.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"mime" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/selector" | ||||
| 	api "github.com/micro/micro/api/proto" | ||||
| ) | ||||
|  | ||||
| func requestToProto(r *http.Request) (*api.Request, error) { | ||||
| 	if err := r.ParseForm(); err != nil { | ||||
| 		return nil, fmt.Errorf("Error parsing form: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	req := &api.Request{ | ||||
| 		Path:   r.URL.Path, | ||||
| 		Method: r.Method, | ||||
| 		Header: make(map[string]*api.Pair), | ||||
| 		Get:    make(map[string]*api.Pair), | ||||
| 		Post:   make(map[string]*api.Pair), | ||||
| 		Url:    r.URL.String(), | ||||
| 	} | ||||
|  | ||||
| 	ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) | ||||
| 	if err != nil { | ||||
| 		ct = "application/x-www-form-urlencoded" | ||||
| 		r.Header.Set("Content-Type", ct) | ||||
| 	} | ||||
|  | ||||
| 	switch ct { | ||||
| 	case "application/x-www-form-urlencoded": | ||||
| 		// expect form vals | ||||
| 	default: | ||||
| 		data, _ := ioutil.ReadAll(r.Body) | ||||
| 		req.Body = string(data) | ||||
| 	} | ||||
|  | ||||
| 	// Set X-Forwarded-For if it does not exist | ||||
| 	if ip, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { | ||||
| 		if prior, ok := r.Header["X-Forwarded-For"]; ok { | ||||
| 			ip = strings.Join(prior, ", ") + ", " + ip | ||||
| 		} | ||||
|  | ||||
| 		// Set the header | ||||
| 		req.Header["X-Forwarded-For"] = &api.Pair{ | ||||
| 			Key:    "X-Forwarded-For", | ||||
| 			Values: []string{ip}, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Host is stripped from net/http Headers so let's add it | ||||
| 	req.Header["Host"] = &api.Pair{ | ||||
| 		Key:    "Host", | ||||
| 		Values: []string{r.Host}, | ||||
| 	} | ||||
|  | ||||
| 	// Get data | ||||
| 	for key, vals := range r.URL.Query() { | ||||
| 		header, ok := req.Get[key] | ||||
| 		if !ok { | ||||
| 			header = &api.Pair{ | ||||
| 				Key: key, | ||||
| 			} | ||||
| 			req.Get[key] = header | ||||
| 		} | ||||
| 		header.Values = vals | ||||
| 	} | ||||
|  | ||||
| 	// Post data | ||||
| 	for key, vals := range r.PostForm { | ||||
| 		header, ok := req.Post[key] | ||||
| 		if !ok { | ||||
| 			header = &api.Pair{ | ||||
| 				Key: key, | ||||
| 			} | ||||
| 			req.Post[key] = header | ||||
| 		} | ||||
| 		header.Values = vals | ||||
| 	} | ||||
|  | ||||
| 	for key, vals := range r.Header { | ||||
| 		header, ok := req.Header[key] | ||||
| 		if !ok { | ||||
| 			header = &api.Pair{ | ||||
| 				Key: key, | ||||
| 			} | ||||
| 			req.Header[key] = header | ||||
| 		} | ||||
| 		header.Values = vals | ||||
| 	} | ||||
|  | ||||
| 	return req, nil | ||||
| } | ||||
|  | ||||
| // strategy is a hack for selection | ||||
| func strategy(services []*registry.Service) selector.Strategy { | ||||
| 	return func(_ []*registry.Service) selector.Next { | ||||
| 		// ignore input to this function, use services above | ||||
| 		return selector.Random(services) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										46
									
								
								api/handler/api/util_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								api/handler/api/util_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestRequestToProto(t *testing.T) { | ||||
| 	testData := []*http.Request{ | ||||
| 		&http.Request{ | ||||
| 			Method: "GET", | ||||
| 			Header: http.Header{ | ||||
| 				"Header": []string{"test"}, | ||||
| 			}, | ||||
| 			URL: &url.URL{ | ||||
| 				Scheme:   "http", | ||||
| 				Host:     "localhost", | ||||
| 				Path:     "/foo/bar", | ||||
| 				RawQuery: "param1=value1", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, d := range testData { | ||||
| 		p, err := requestToProto(d) | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 		if p.Path != d.URL.Path { | ||||
| 			t.Fatalf("Expected path %s got %s", d.URL.Path, p.Path) | ||||
| 		} | ||||
| 		if p.Method != d.Method { | ||||
| 			t.Fatalf("Expected method %s got %s", d.Method, p.Method) | ||||
| 		} | ||||
| 		for k, v := range d.Header { | ||||
| 			if val, ok := p.Header[k]; !ok { | ||||
| 				t.Fatalf("Expected header %s", k) | ||||
| 			} else { | ||||
| 				if val.Values[0] != v[0] { | ||||
| 					t.Fatalf("Expected val %s, got %s", val.Values[0], v[0]) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										268
									
								
								api/handler/broker/broker.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								api/handler/broker/broker.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,268 @@ | ||||
| // Package broker provides a go-micro/broker handler | ||||
| package broker | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/gorilla/websocket" | ||||
| 	"github.com/micro/go-micro/api/handler" | ||||
| 	"github.com/micro/go-micro/broker" | ||||
| 	"github.com/micro/go-micro/util/log" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	Handler = "broker" | ||||
|  | ||||
| 	pingTime      = (readDeadline * 9) / 10 | ||||
| 	readLimit     = 16384 | ||||
| 	readDeadline  = 60 * time.Second | ||||
| 	writeDeadline = 10 * time.Second | ||||
| ) | ||||
|  | ||||
| type brokerHandler struct { | ||||
| 	opts handler.Options | ||||
| 	u    websocket.Upgrader | ||||
| } | ||||
|  | ||||
| type conn struct { | ||||
| 	b     broker.Broker | ||||
| 	cType string | ||||
| 	topic string | ||||
| 	queue string | ||||
| 	exit  chan bool | ||||
|  | ||||
| 	sync.Mutex | ||||
| 	ws *websocket.Conn | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	once        sync.Once | ||||
| 	contentType = "text/plain" | ||||
| ) | ||||
|  | ||||
| func checkOrigin(r *http.Request) bool { | ||||
| 	origin := r.Header["Origin"] | ||||
| 	if len(origin) == 0 { | ||||
| 		return true | ||||
| 	} | ||||
| 	u, err := url.Parse(origin[0]) | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	return u.Host == r.Host | ||||
| } | ||||
|  | ||||
| func (c *conn) close() { | ||||
| 	select { | ||||
| 	case <-c.exit: | ||||
| 		return | ||||
| 	default: | ||||
| 		close(c.exit) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *conn) readLoop() { | ||||
| 	defer func() { | ||||
| 		c.close() | ||||
| 		c.ws.Close() | ||||
| 	}() | ||||
|  | ||||
| 	// set read limit/deadline | ||||
| 	c.ws.SetReadLimit(readLimit) | ||||
| 	c.ws.SetReadDeadline(time.Now().Add(readDeadline)) | ||||
|  | ||||
| 	// set close handler | ||||
| 	ch := c.ws.CloseHandler() | ||||
| 	c.ws.SetCloseHandler(func(code int, text string) error { | ||||
| 		err := ch(code, text) | ||||
| 		c.close() | ||||
| 		return err | ||||
| 	}) | ||||
|  | ||||
| 	// set pong handler | ||||
| 	c.ws.SetPongHandler(func(string) error { | ||||
| 		c.ws.SetReadDeadline(time.Now().Add(readDeadline)) | ||||
| 		return nil | ||||
| 	}) | ||||
|  | ||||
| 	for { | ||||
| 		_, message, err := c.ws.ReadMessage() | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		c.b.Publish(c.topic, &broker.Message{ | ||||
| 			Header: map[string]string{"Content-Type": c.cType}, | ||||
| 			Body:   message, | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *conn) write(mType int, data []byte) error { | ||||
| 	c.Lock() | ||||
| 	c.ws.SetWriteDeadline(time.Now().Add(writeDeadline)) | ||||
| 	err := c.ws.WriteMessage(mType, data) | ||||
| 	c.Unlock() | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (c *conn) writeLoop() { | ||||
| 	ticker := time.NewTicker(pingTime) | ||||
|  | ||||
| 	var opts []broker.SubscribeOption | ||||
|  | ||||
| 	if len(c.queue) > 0 { | ||||
| 		opts = append(opts, broker.Queue(c.queue)) | ||||
| 	} | ||||
|  | ||||
| 	subscriber, err := c.b.Subscribe(c.topic, func(p broker.Publication) error { | ||||
| 		b, err := json.Marshal(p.Message()) | ||||
| 		if err != nil { | ||||
| 			return nil | ||||
| 		} | ||||
| 		return c.write(websocket.TextMessage, b) | ||||
| 	}, opts...) | ||||
|  | ||||
| 	defer func() { | ||||
| 		subscriber.Unsubscribe() | ||||
| 		ticker.Stop() | ||||
| 		c.ws.Close() | ||||
| 	}() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		log.Log(err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-ticker.C: | ||||
| 			if err := c.write(websocket.PingMessage, []byte{}); err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 		case <-c.exit: | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *brokerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	br := b.opts.Service.Client().Options().Broker | ||||
|  | ||||
| 	// Setup the broker | ||||
| 	once.Do(func() { | ||||
| 		br.Init() | ||||
| 		br.Connect() | ||||
| 	}) | ||||
|  | ||||
| 	// Parse | ||||
| 	r.ParseForm() | ||||
| 	topic := r.Form.Get("topic") | ||||
|  | ||||
| 	// Can't do anything without a topic | ||||
| 	if len(topic) == 0 { | ||||
| 		http.Error(w, "Topic not specified", 400) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Post assumed to be Publish | ||||
| 	if r.Method == "POST" { | ||||
| 		// Create a broker message | ||||
| 		msg := &broker.Message{ | ||||
| 			Header: make(map[string]string), | ||||
| 		} | ||||
|  | ||||
| 		// Set header | ||||
| 		for k, v := range r.Header { | ||||
| 			msg.Header[k] = strings.Join(v, ", ") | ||||
| 		} | ||||
|  | ||||
| 		// Read body | ||||
| 		b, err := ioutil.ReadAll(r.Body) | ||||
| 		if err != nil { | ||||
| 			http.Error(w, err.Error(), 500) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// Set body | ||||
| 		msg.Body = b | ||||
|  | ||||
| 		// Publish | ||||
| 		br.Publish(topic, msg) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// now back to our regularly scheduled programming | ||||
|  | ||||
| 	if r.Method != "GET" { | ||||
| 		http.Error(w, "Method not allowed", 405) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	queue := r.Form.Get("queue") | ||||
|  | ||||
| 	ws, err := b.u.Upgrade(w, r, nil) | ||||
| 	if err != nil { | ||||
| 		log.Log(err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	cType := r.Header.Get("Content-Type") | ||||
| 	if len(cType) == 0 { | ||||
| 		cType = contentType | ||||
| 	} | ||||
|  | ||||
| 	c := &conn{ | ||||
| 		b:     br, | ||||
| 		cType: cType, | ||||
| 		topic: topic, | ||||
| 		queue: queue, | ||||
| 		exit:  make(chan bool), | ||||
| 		ws:    ws, | ||||
| 	} | ||||
|  | ||||
| 	go c.writeLoop() | ||||
| 	c.readLoop() | ||||
| } | ||||
|  | ||||
| func (b *brokerHandler) String() string { | ||||
| 	return "broker" | ||||
| } | ||||
|  | ||||
| func NewHandler(opts ...handler.Option) handler.Handler { | ||||
| 	return &brokerHandler{ | ||||
| 		u: websocket.Upgrader{ | ||||
| 			CheckOrigin: func(r *http.Request) bool { | ||||
| 				return true | ||||
| 			}, | ||||
| 			ReadBufferSize:  1024, | ||||
| 			WriteBufferSize: 1024, | ||||
| 		}, | ||||
| 		opts: handler.NewOptions(opts...), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func WithCors(cors map[string]bool, opts ...handler.Option) handler.Handler { | ||||
| 	return &brokerHandler{ | ||||
| 		u: websocket.Upgrader{ | ||||
| 			CheckOrigin: func(r *http.Request) bool { | ||||
| 				if origin := r.Header.Get("Origin"); cors[origin] { | ||||
| 					return true | ||||
| 				} else if len(origin) > 0 && cors["*"] { | ||||
| 					return true | ||||
| 				} else if checkOrigin(r) { | ||||
| 					return true | ||||
| 				} | ||||
| 				return false | ||||
| 			}, | ||||
| 			ReadBufferSize:  1024, | ||||
| 			WriteBufferSize: 1024, | ||||
| 		}, | ||||
| 		opts: handler.NewOptions(opts...), | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										94
									
								
								api/handler/cloudevents/cloudevents.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								api/handler/cloudevents/cloudevents.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| // Package cloudevents provides a cloudevents handler publishing the event using the go-micro/client | ||||
| package cloudevents | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"path" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/micro/go-micro/api/handler" | ||||
| 	"github.com/micro/go-micro/util/ctx" | ||||
| ) | ||||
|  | ||||
| type event struct { | ||||
| 	options handler.Options | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	Handler   = "cloudevents" | ||||
| 	versionRe = regexp.MustCompilePOSIX("^v[0-9]+$") | ||||
| ) | ||||
|  | ||||
| func eventName(parts []string) string { | ||||
| 	return strings.Join(parts, ".") | ||||
| } | ||||
|  | ||||
| func evRoute(ns, p string) (string, string) { | ||||
| 	p = path.Clean(p) | ||||
| 	p = strings.TrimPrefix(p, "/") | ||||
|  | ||||
| 	if len(p) == 0 { | ||||
| 		return ns, "event" | ||||
| 	} | ||||
|  | ||||
| 	parts := strings.Split(p, "/") | ||||
|  | ||||
| 	// no path | ||||
| 	if len(parts) == 0 { | ||||
| 		// topic: namespace | ||||
| 		// action: event | ||||
| 		return strings.Trim(ns, "."), "event" | ||||
| 	} | ||||
|  | ||||
| 	// Treat /v[0-9]+ as versioning | ||||
| 	// /v1/foo/bar => topic: v1.foo action: bar | ||||
| 	if len(parts) >= 2 && versionRe.Match([]byte(parts[0])) { | ||||
| 		topic := ns + "." + strings.Join(parts[:2], ".") | ||||
| 		action := eventName(parts[1:]) | ||||
| 		return topic, action | ||||
| 	} | ||||
|  | ||||
| 	// /foo => topic: ns.foo action: foo | ||||
| 	// /foo/bar => topic: ns.foo action: bar | ||||
| 	topic := ns + "." + strings.Join(parts[:1], ".") | ||||
| 	action := eventName(parts[1:]) | ||||
|  | ||||
| 	return topic, action | ||||
| } | ||||
|  | ||||
| func (e *event) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	// request to topic:event | ||||
| 	// create event | ||||
| 	// publish to topic | ||||
| 	topic, _ := evRoute(e.options.Namespace, r.URL.Path) | ||||
|  | ||||
| 	// create event | ||||
| 	ev, err := FromRequest(r) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// get client | ||||
| 	c := e.options.Service.Client() | ||||
|  | ||||
| 	// create publication | ||||
| 	p := c.NewMessage(topic, ev) | ||||
|  | ||||
| 	// publish event | ||||
| 	if err := c.Publish(ctx.FromRequest(r), p); err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *event) String() string { | ||||
| 	return "cloudevents" | ||||
| } | ||||
|  | ||||
| func NewHandler(opts ...handler.Option) handler.Handler { | ||||
| 	return &event{ | ||||
| 		options: handler.NewOptions(opts...), | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										282
									
								
								api/handler/cloudevents/event.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								api/handler/cloudevents/event.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,282 @@ | ||||
| /* | ||||
|  * From: https://github.com/serverless/event-gateway/blob/master/event/event.go | ||||
|  * Modified: Strip to handler requirements | ||||
|  * | ||||
|  * Copyright 2017 Serverless, Inc. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| package cloudevents | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"mime" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 	"unicode" | ||||
|  | ||||
| 	"github.com/pborman/uuid" | ||||
| 	"gopkg.in/go-playground/validator.v9" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// TransformationVersion is indicative of the revision of how Event Gateway transforms a request into CloudEvents format. | ||||
| 	TransformationVersion = "0.1" | ||||
|  | ||||
| 	// CloudEventsVersion currently supported by Event Gateway | ||||
| 	CloudEventsVersion = "0.1" | ||||
| ) | ||||
|  | ||||
| // Event is a default event structure. All data that passes through the Event Gateway | ||||
| // is formatted to a format defined CloudEvents v0.1 spec. | ||||
| type Event struct { | ||||
| 	EventType          string                 `json:"eventType" validate:"required"` | ||||
| 	EventTypeVersion   string                 `json:"eventTypeVersion,omitempty"` | ||||
| 	CloudEventsVersion string                 `json:"cloudEventsVersion" validate:"required"` | ||||
| 	Source             string                 `json:"source" validate:"uri,required"` | ||||
| 	EventID            string                 `json:"eventID" validate:"required"` | ||||
| 	EventTime          *time.Time             `json:"eventTime,omitempty"` | ||||
| 	SchemaURL          string                 `json:"schemaURL,omitempty"` | ||||
| 	Extensions         map[string]interface{} `json:"extensions,omitempty"` | ||||
| 	ContentType        string                 `json:"contentType,omitempty"` | ||||
| 	Data               interface{}            `json:"data"` | ||||
| } | ||||
|  | ||||
| // New return new instance of Event. | ||||
| func New(eventType string, mimeType string, payload interface{}) *Event { | ||||
| 	now := time.Now() | ||||
|  | ||||
| 	event := &Event{ | ||||
| 		EventType:          eventType, | ||||
| 		CloudEventsVersion: CloudEventsVersion, | ||||
| 		Source:             "https://micro.mu", | ||||
| 		EventID:            uuid.NewUUID().String(), | ||||
| 		EventTime:          &now, | ||||
| 		ContentType:        mimeType, | ||||
| 		Data:               payload, | ||||
| 		Extensions: map[string]interface{}{ | ||||
| 			"eventgateway": map[string]interface{}{ | ||||
| 				"transformed":            "true", | ||||
| 				"transformation-version": TransformationVersion, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	event.Data = normalizePayload(event.Data, event.ContentType) | ||||
| 	return event | ||||
| } | ||||
|  | ||||
| // FromRequest takes an HTTP request and returns an Event along with path. Most of the implementation | ||||
| // is based on https://github.com/cloudevents/spec/blob/master/http-transport-binding.md. | ||||
| // This function also supports legacy mode where event type is sent in Event header. | ||||
| func FromRequest(r *http.Request) (*Event, error) { | ||||
| 	contentType := r.Header.Get("Content-Type") | ||||
| 	mimeType, _, err := mime.ParseMediaType(contentType) | ||||
| 	if err != nil { | ||||
| 		if err.Error() != "mime: no media type" { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		mimeType = "application/octet-stream" | ||||
| 	} | ||||
| 	// Read request body | ||||
| 	body := []byte{} | ||||
| 	if r.Body != nil { | ||||
| 		body, err = ioutil.ReadAll(r.Body) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	var event *Event | ||||
| 	if mimeType == mimeCloudEventsJSON { // CloudEvents Structured Content Mode | ||||
| 		return parseAsCloudEvent(mimeType, body) | ||||
| 	} else if isCloudEventsBinaryContentMode(r.Header) { // CloudEvents Binary Content Mode | ||||
| 		return parseAsCloudEventBinary(r.Header, body) | ||||
| 	} else if isLegacyMode(r.Header) { | ||||
| 		if mimeType == mimeJSON { // CloudEvent in Legacy Mode | ||||
| 			event, err = parseAsCloudEvent(mimeType, body) | ||||
| 			if err != nil { | ||||
| 				return New(string(r.Header.Get("event")), mimeType, body), nil | ||||
| 			} | ||||
| 			return event, err | ||||
| 		} | ||||
|  | ||||
| 		return New(string(r.Header.Get("event")), mimeType, body), nil | ||||
| 	} | ||||
|  | ||||
| 	return New("http.request", mimeJSON, newHTTPRequestData(r, body)), nil | ||||
| } | ||||
|  | ||||
| // Validate Event struct | ||||
| func (e *Event) Validate() error { | ||||
| 	validate := validator.New() | ||||
| 	err := validate.Struct(e) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("CloudEvent not valid: %v", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func isLegacyMode(headers http.Header) bool { | ||||
| 	if headers.Get("Event") != "" { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func isCloudEventsBinaryContentMode(headers http.Header) bool { | ||||
| 	if headers.Get("CE-EventType") != "" && | ||||
| 		headers.Get("CE-CloudEventsVersion") != "" && | ||||
| 		headers.Get("CE-Source") != "" && | ||||
| 		headers.Get("CE-EventID") != "" { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func parseAsCloudEventBinary(headers http.Header, payload interface{}) (*Event, error) { | ||||
| 	event := &Event{ | ||||
| 		EventType:          headers.Get("CE-EventType"), | ||||
| 		EventTypeVersion:   headers.Get("CE-EventTypeVersion"), | ||||
| 		CloudEventsVersion: headers.Get("CE-CloudEventsVersion"), | ||||
| 		Source:             headers.Get("CE-Source"), | ||||
| 		EventID:            headers.Get("CE-EventID"), | ||||
| 		ContentType:        headers.Get("Content-Type"), | ||||
| 		Data:               payload, | ||||
| 	} | ||||
|  | ||||
| 	err := event.Validate() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if headers.Get("CE-EventTime") != "" { | ||||
| 		val, err := time.Parse(time.RFC3339, headers.Get("CE-EventTime")) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		event.EventTime = &val | ||||
| 	} | ||||
|  | ||||
| 	if val := headers.Get("CE-SchemaURL"); len(val) > 0 { | ||||
| 		event.SchemaURL = val | ||||
| 	} | ||||
|  | ||||
| 	event.Extensions = map[string]interface{}{} | ||||
| 	for key, val := range flatten(headers) { | ||||
| 		if strings.HasPrefix(key, "Ce-X-") { | ||||
| 			key = strings.TrimLeft(key, "Ce-X-") | ||||
| 			// Make first character lowercase | ||||
| 			runes := []rune(key) | ||||
| 			runes[0] = unicode.ToLower(runes[0]) | ||||
| 			event.Extensions[string(runes)] = val | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	event.Data = normalizePayload(event.Data, event.ContentType) | ||||
| 	return event, nil | ||||
| } | ||||
|  | ||||
| func flatten(h http.Header) map[string]string { | ||||
| 	headers := map[string]string{} | ||||
| 	for key, header := range h { | ||||
| 		headers[key] = header[0] | ||||
| 		if len(header) > 1 { | ||||
| 			headers[key] = strings.Join(header, ", ") | ||||
| 		} | ||||
| 	} | ||||
| 	return headers | ||||
| } | ||||
|  | ||||
| func parseAsCloudEvent(mime string, payload interface{}) (*Event, error) { | ||||
| 	body, ok := payload.([]byte) | ||||
| 	if ok { | ||||
| 		event := &Event{} | ||||
| 		err := json.Unmarshal(body, event) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		err = event.Validate() | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		event.Data = normalizePayload(event.Data, event.ContentType) | ||||
| 		return event, nil | ||||
| 	} | ||||
|  | ||||
| 	return nil, errors.New("couldn't cast to []byte") | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	mimeJSON            = "application/json" | ||||
| 	mimeFormMultipart   = "multipart/form-data" | ||||
| 	mimeFormURLEncoded  = "application/x-www-form-urlencoded" | ||||
| 	mimeCloudEventsJSON = "application/cloudevents+json" | ||||
| ) | ||||
|  | ||||
| // normalizePayload takes anything, checks if it's []byte array and depending on provided mime | ||||
| // type converts it to either string or map[string]interface to avoid having base64 string after | ||||
| // JSON marshaling. | ||||
| func normalizePayload(payload interface{}, mime string) interface{} { | ||||
| 	if bytePayload, ok := payload.([]byte); ok && len(bytePayload) > 0 { | ||||
| 		switch { | ||||
| 		case mime == mimeJSON || strings.HasSuffix(mime, "+json"): | ||||
| 			var result map[string]interface{} | ||||
| 			err := json.Unmarshal(bytePayload, &result) | ||||
| 			if err != nil { | ||||
| 				return payload | ||||
| 			} | ||||
| 			return result | ||||
| 		case strings.HasPrefix(mime, mimeFormMultipart), mime == mimeFormURLEncoded: | ||||
| 			return string(bytePayload) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return payload | ||||
| } | ||||
|  | ||||
| // HTTPRequestData is a event schema used for sending events to HTTP subscriptions. | ||||
| type HTTPRequestData struct { | ||||
| 	Headers map[string]string   `json:"headers"` | ||||
| 	Query   map[string][]string `json:"query"` | ||||
| 	Body    interface{}         `json:"body"` | ||||
| 	Host    string              `json:"host"` | ||||
| 	Path    string              `json:"path"` | ||||
| 	Method  string              `json:"method"` | ||||
| 	Params  map[string]string   `json:"params"` | ||||
| } | ||||
|  | ||||
| // NewHTTPRequestData returns a new instance of HTTPRequestData | ||||
| func newHTTPRequestData(r *http.Request, eventData interface{}) *HTTPRequestData { | ||||
| 	req := &HTTPRequestData{ | ||||
| 		Headers: flatten(r.Header), | ||||
| 		Query:   r.URL.Query(), | ||||
| 		Body:    eventData, | ||||
| 		Host:    r.Host, | ||||
| 		Path:    r.URL.Path, | ||||
| 		Method:  r.Method, | ||||
| 	} | ||||
|  | ||||
| 	req.Body = normalizePayload(req.Body, r.Header.Get("content-type")) | ||||
| 	return req | ||||
| } | ||||
							
								
								
									
										122
									
								
								api/handler/event/event.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								api/handler/event/event.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| // Package event provides a handler which publishes an event | ||||
| package event | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"path" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/micro/go-micro/api/handler" | ||||
| 	proto "github.com/micro/go-micro/api/proto" | ||||
| 	"github.com/micro/go-micro/util/ctx" | ||||
| 	"github.com/pborman/uuid" | ||||
| ) | ||||
|  | ||||
| type event struct { | ||||
| 	options handler.Options | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	Handler   = "event" | ||||
| 	versionRe = regexp.MustCompilePOSIX("^v[0-9]+$") | ||||
| ) | ||||
|  | ||||
| func eventName(parts []string) string { | ||||
| 	return strings.Join(parts, ".") | ||||
| } | ||||
|  | ||||
| func evRoute(ns, p string) (string, string) { | ||||
| 	p = path.Clean(p) | ||||
| 	p = strings.TrimPrefix(p, "/") | ||||
|  | ||||
| 	if len(p) == 0 { | ||||
| 		return ns, "event" | ||||
| 	} | ||||
|  | ||||
| 	parts := strings.Split(p, "/") | ||||
|  | ||||
| 	// no path | ||||
| 	if len(parts) == 0 { | ||||
| 		// topic: namespace | ||||
| 		// action: event | ||||
| 		return strings.Trim(ns, "."), "event" | ||||
| 	} | ||||
|  | ||||
| 	// Treat /v[0-9]+ as versioning | ||||
| 	// /v1/foo/bar => topic: v1.foo action: bar | ||||
| 	if len(parts) >= 2 && versionRe.Match([]byte(parts[0])) { | ||||
| 		topic := ns + "." + strings.Join(parts[:2], ".") | ||||
| 		action := eventName(parts[1:]) | ||||
| 		return topic, action | ||||
| 	} | ||||
|  | ||||
| 	// /foo => topic: ns.foo action: foo | ||||
| 	// /foo/bar => topic: ns.foo action: bar | ||||
| 	topic := ns + "." + strings.Join(parts[:1], ".") | ||||
| 	action := eventName(parts[1:]) | ||||
|  | ||||
| 	return topic, action | ||||
| } | ||||
|  | ||||
| func (e *event) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	// request to topic:event | ||||
| 	// create event | ||||
| 	// publish to topic | ||||
|  | ||||
| 	topic, action := evRoute(e.options.Namespace, r.URL.Path) | ||||
|  | ||||
| 	// create event | ||||
| 	ev := &proto.Event{ | ||||
| 		Name: action, | ||||
| 		// TODO: dedupe event | ||||
| 		Id:        fmt.Sprintf("%s-%s-%s", topic, action, uuid.NewUUID().String()), | ||||
| 		Header:    make(map[string]*proto.Pair), | ||||
| 		Timestamp: time.Now().Unix(), | ||||
| 	} | ||||
|  | ||||
| 	// set headers | ||||
| 	for key, vals := range r.Header { | ||||
| 		header, ok := ev.Header[key] | ||||
| 		if !ok { | ||||
| 			header = &proto.Pair{ | ||||
| 				Key: key, | ||||
| 			} | ||||
| 			ev.Header[key] = header | ||||
| 		} | ||||
| 		header.Values = vals | ||||
| 	} | ||||
|  | ||||
| 	// set body | ||||
| 	b, err := ioutil.ReadAll(r.Body) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
| 	ev.Data = string(b) | ||||
|  | ||||
| 	// get client | ||||
| 	c := e.options.Service.Client() | ||||
|  | ||||
| 	// create publication | ||||
| 	p := c.NewMessage(topic, ev) | ||||
|  | ||||
| 	// publish event | ||||
| 	if err := c.Publish(ctx.FromRequest(r), p); err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *event) String() string { | ||||
| 	return "event" | ||||
| } | ||||
|  | ||||
| func NewHandler(opts ...handler.Option) handler.Handler { | ||||
| 	return &event{ | ||||
| 		options: handler.NewOptions(opts...), | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										16
									
								
								api/handler/file/file.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								api/handler/file/file.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| // Package file serves file relative to the current directory | ||||
| package file | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| type Handler struct{} | ||||
|  | ||||
| func (h *Handler) Serve(w http.ResponseWriter, r *http.Request) { | ||||
| 	http.ServeFile(w, r, "."+r.URL.Path) | ||||
| } | ||||
|  | ||||
| func (h *Handler) String() string { | ||||
| 	return "file" | ||||
| } | ||||
							
								
								
									
										14
									
								
								api/handler/handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								api/handler/handler.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| // Package handler provides http handlers | ||||
| package handler | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| // Handler represents a HTTP handler that manages a request | ||||
| type Handler interface { | ||||
| 	// standard http handler | ||||
| 	http.Handler | ||||
| 	// name of handler | ||||
| 	String() string | ||||
| } | ||||
							
								
								
									
										100
									
								
								api/handler/http/http.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								api/handler/http/http.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| // Package http is a http reverse proxy handler | ||||
| package http | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/http/httputil" | ||||
| 	"net/url" | ||||
|  | ||||
| 	"github.com/micro/go-micro/api" | ||||
| 	"github.com/micro/go-micro/api/handler" | ||||
| 	"github.com/micro/go-micro/selector" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	Handler = "http" | ||||
| ) | ||||
|  | ||||
| type httpHandler struct { | ||||
| 	options handler.Options | ||||
|  | ||||
| 	// set with different initialiser | ||||
| 	s *api.Service | ||||
| } | ||||
|  | ||||
| func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	service, err := h.getService(r) | ||||
| 	if err != nil { | ||||
| 		w.WriteHeader(500) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if len(service) == 0 { | ||||
| 		w.WriteHeader(404) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	rp, err := url.Parse(service) | ||||
| 	if err != nil { | ||||
| 		w.WriteHeader(500) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	httputil.NewSingleHostReverseProxy(rp).ServeHTTP(w, r) | ||||
| } | ||||
|  | ||||
| // getService returns the service for this request from the selector | ||||
| func (h *httpHandler) getService(r *http.Request) (string, error) { | ||||
| 	var service *api.Service | ||||
|  | ||||
| 	if h.s != nil { | ||||
| 		// we were given the service | ||||
| 		service = h.s | ||||
| 	} else if h.options.Router != nil { | ||||
| 		// try get service from router | ||||
| 		s, err := h.options.Router.Route(r) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		service = s | ||||
| 	} else { | ||||
| 		// we have no way of routing the request | ||||
| 		return "", errors.New("no route found") | ||||
| 	} | ||||
|  | ||||
| 	// create a random selector | ||||
| 	next := selector.Random(service.Services) | ||||
|  | ||||
| 	// get the next node | ||||
| 	s, err := next() | ||||
| 	if err != nil { | ||||
| 		return "", nil | ||||
| 	} | ||||
|  | ||||
| 	return fmt.Sprintf("http://%s:%d", s.Address, s.Port), nil | ||||
| } | ||||
|  | ||||
| func (h *httpHandler) String() string { | ||||
| 	return "http" | ||||
| } | ||||
|  | ||||
| // NewHandler returns a http proxy handler | ||||
| func NewHandler(opts ...handler.Option) handler.Handler { | ||||
| 	options := handler.NewOptions(opts...) | ||||
|  | ||||
| 	return &httpHandler{ | ||||
| 		options: options, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithService creates a handler with a service | ||||
| func WithService(s *api.Service, opts ...handler.Option) handler.Handler { | ||||
| 	options := handler.NewOptions(opts...) | ||||
|  | ||||
| 	return &httpHandler{ | ||||
| 		options: options, | ||||
| 		s:       s, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										133
									
								
								api/handler/http/http_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								api/handler/http/http_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| package http | ||||
|  | ||||
| import ( | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/http/httptest" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/micro/go-micro/api/handler" | ||||
| 	"github.com/micro/go-micro/api/router" | ||||
| 	regRouter "github.com/micro/go-micro/api/router/registry" | ||||
| 	"github.com/micro/go-micro/cmd" | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/registry/memory" | ||||
| ) | ||||
|  | ||||
| func testHttp(t *testing.T, path, service, ns string) { | ||||
| 	r := memory.NewRegistry() | ||||
| 	cmd.DefaultCmd = cmd.NewCmd(cmd.Registry(&r)) | ||||
|  | ||||
| 	l, err := net.Listen("tcp", "127.0.0.1:0") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer l.Close() | ||||
|  | ||||
| 	parts := strings.Split(l.Addr().String(), ":") | ||||
|  | ||||
| 	var host string | ||||
| 	var port int | ||||
|  | ||||
| 	host = parts[0] | ||||
| 	port, _ = strconv.Atoi(parts[1]) | ||||
|  | ||||
| 	s := ®istry.Service{ | ||||
| 		Name: service, | ||||
| 		Nodes: []*registry.Node{ | ||||
| 			®istry.Node{ | ||||
| 				Id:      service + "-1", | ||||
| 				Address: host, | ||||
| 				Port:    port, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	r.Register(s) | ||||
| 	defer r.Deregister(s) | ||||
|  | ||||
| 	// setup the test handler | ||||
| 	m := http.NewServeMux() | ||||
| 	m.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { | ||||
| 		w.Write([]byte(`you got served`)) | ||||
| 	}) | ||||
|  | ||||
| 	// start http test serve | ||||
| 	go http.Serve(l, m) | ||||
|  | ||||
| 	// create new request and writer | ||||
| 	w := httptest.NewRecorder() | ||||
| 	req, err := http.NewRequest("POST", path, nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// initialise the handler | ||||
| 	rt := regRouter.NewRouter( | ||||
| 		router.WithHandler("http"), | ||||
| 		router.WithNamespace(ns), | ||||
| 	) | ||||
|  | ||||
| 	p := NewHandler(handler.WithRouter(rt)) | ||||
|  | ||||
| 	// execute the handler | ||||
| 	p.ServeHTTP(w, req) | ||||
|  | ||||
| 	if w.Code != 200 { | ||||
| 		t.Fatalf("Expected 200 response got %d %s", w.Code, w.Body.String()) | ||||
| 	} | ||||
|  | ||||
| 	if w.Body.String() != "you got served" { | ||||
| 		t.Fatalf("Expected body: you got served. Got: %s", w.Body.String()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestHttpHandler(t *testing.T) { | ||||
| 	testData := []struct { | ||||
| 		path      string | ||||
| 		service   string | ||||
| 		namespace string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			"/test/foo", | ||||
| 			"go.micro.api.test", | ||||
| 			"go.micro.api", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/test/foo/baz", | ||||
| 			"go.micro.api.test", | ||||
| 			"go.micro.api", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo", | ||||
| 			"go.micro.api.v1.foo", | ||||
| 			"go.micro.api", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo/bar", | ||||
| 			"go.micro.api.v1.foo", | ||||
| 			"go.micro.api", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v2/baz", | ||||
| 			"go.micro.api.v2.baz", | ||||
| 			"go.micro.api", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v2/baz/bar", | ||||
| 			"go.micro.api.v2.baz", | ||||
| 			"go.micro.api", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v2/baz/bar", | ||||
| 			"v2.baz", | ||||
| 			"", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, d := range testData { | ||||
| 		testHttp(t, d.path, d.service, d.namespace) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										55
									
								
								api/handler/options.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								api/handler/options.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| package handler | ||||
|  | ||||
| import ( | ||||
| 	"github.com/micro/go-micro" | ||||
| 	"github.com/micro/go-micro/api/router" | ||||
| ) | ||||
|  | ||||
| type Options struct { | ||||
| 	Namespace string | ||||
| 	Router    router.Router | ||||
| 	Service   micro.Service | ||||
| } | ||||
|  | ||||
| type Option func(o *Options) | ||||
|  | ||||
| // NewOptions fills in the blanks | ||||
| func NewOptions(opts ...Option) Options { | ||||
| 	var options Options | ||||
| 	for _, o := range opts { | ||||
| 		o(&options) | ||||
| 	} | ||||
|  | ||||
| 	// create service if its blank | ||||
| 	if options.Service == nil { | ||||
| 		WithService(micro.NewService())(&options) | ||||
| 	} | ||||
|  | ||||
| 	// set namespace if blank | ||||
| 	if len(options.Namespace) == 0 { | ||||
| 		WithNamespace("go.micro.api")(&options) | ||||
| 	} | ||||
|  | ||||
| 	return options | ||||
| } | ||||
|  | ||||
| // WithNamespace specifies the namespace for the handler | ||||
| func WithNamespace(s string) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.Namespace = s | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithRouter specifies a router to be used by the handler | ||||
| func WithRouter(r router.Router) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.Router = r | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithService specifies a micro.Service | ||||
| func WithService(s micro.Service) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.Service = s | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										211
									
								
								api/handler/registry/registry.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								api/handler/registry/registry.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,211 @@ | ||||
| // Package registry is a go-micro/registry handler | ||||
| package registry | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/gorilla/websocket" | ||||
| 	"github.com/micro/go-micro/api/handler" | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	Handler = "registry" | ||||
|  | ||||
| 	pingTime      = (readDeadline * 9) / 10 | ||||
| 	readLimit     = 16384 | ||||
| 	readDeadline  = 60 * time.Second | ||||
| 	writeDeadline = 10 * time.Second | ||||
| ) | ||||
|  | ||||
| type registryHandler struct { | ||||
| 	opts handler.Options | ||||
| 	reg  registry.Registry | ||||
| } | ||||
|  | ||||
| func (rh *registryHandler) add(w http.ResponseWriter, r *http.Request) { | ||||
| 	r.ParseForm() | ||||
| 	b, err := ioutil.ReadAll(r.Body) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
| 	defer r.Body.Close() | ||||
|  | ||||
| 	var opts []registry.RegisterOption | ||||
|  | ||||
| 	// parse ttl | ||||
| 	if ttl := r.Form.Get("ttl"); len(ttl) > 0 { | ||||
| 		d, err := time.ParseDuration(ttl) | ||||
| 		if err == nil { | ||||
| 			opts = append(opts, registry.RegisterTTL(d)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	var service *registry.Service | ||||
| 	err = json.Unmarshal(b, &service) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
| 	err = rh.reg.Register(service, opts...) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (rh *registryHandler) del(w http.ResponseWriter, r *http.Request) { | ||||
| 	r.ParseForm() | ||||
| 	b, err := ioutil.ReadAll(r.Body) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
| 	defer r.Body.Close() | ||||
|  | ||||
| 	var service *registry.Service | ||||
| 	err = json.Unmarshal(b, &service) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
| 	err = rh.reg.Deregister(service) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (rh *registryHandler) get(w http.ResponseWriter, r *http.Request) { | ||||
| 	r.ParseForm() | ||||
| 	service := r.Form.Get("service") | ||||
|  | ||||
| 	var s []*registry.Service | ||||
| 	var err error | ||||
|  | ||||
| 	if len(service) == 0 { | ||||
| 		// | ||||
| 		upgrade := r.Header.Get("Upgrade") | ||||
| 		connect := r.Header.Get("Connection") | ||||
|  | ||||
| 		// watch if websockets | ||||
| 		if upgrade == "websocket" && connect == "Upgrade" { | ||||
| 			rw, err := rh.reg.Watch() | ||||
| 			if err != nil { | ||||
| 				http.Error(w, err.Error(), 500) | ||||
| 				return | ||||
| 			} | ||||
| 			watch(rw, w, r) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// otherwise list services | ||||
| 		s, err = rh.reg.ListServices() | ||||
| 	} else { | ||||
| 		s, err = rh.reg.GetService(service) | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if s == nil || (len(service) > 0 && (len(s) == 0 || len(s[0].Name) == 0)) { | ||||
| 		http.Error(w, "Service not found", 404) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	b, err := json.Marshal(s) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	w.Header().Set("Content-Type", "application/json") | ||||
| 	w.Header().Set("Content-Length", strconv.Itoa(len(b))) | ||||
| 	w.Write(b) | ||||
| } | ||||
|  | ||||
| func ping(ws *websocket.Conn, exit chan bool) { | ||||
| 	ticker := time.NewTicker(pingTime) | ||||
|  | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-ticker.C: | ||||
| 			ws.SetWriteDeadline(time.Now().Add(writeDeadline)) | ||||
| 			err := ws.WriteMessage(websocket.PingMessage, []byte{}) | ||||
| 			if err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 		case <-exit: | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func watch(rw registry.Watcher, w http.ResponseWriter, r *http.Request) { | ||||
| 	upgrader := websocket.Upgrader{ | ||||
| 		ReadBufferSize:  1024, | ||||
| 		WriteBufferSize: 1024, | ||||
| 	} | ||||
|  | ||||
| 	ws, err := upgrader.Upgrade(w, r, nil) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// we need an exit chan | ||||
| 	exit := make(chan bool) | ||||
|  | ||||
| 	defer func() { | ||||
| 		close(exit) | ||||
| 	}() | ||||
|  | ||||
| 	// ping the socket | ||||
| 	go ping(ws, exit) | ||||
|  | ||||
| 	for { | ||||
| 		// get next result | ||||
| 		r, err := rw.Next() | ||||
| 		if err != nil { | ||||
| 			http.Error(w, err.Error(), 500) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// write to client | ||||
| 		ws.SetWriteDeadline(time.Now().Add(writeDeadline)) | ||||
| 		if err := ws.WriteJSON(r); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (rh *registryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	switch r.Method { | ||||
| 	case "GET": | ||||
| 		rh.get(w, r) | ||||
| 	case "POST": | ||||
| 		rh.add(w, r) | ||||
| 	case "DELETE": | ||||
| 		rh.del(w, r) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (rh *registryHandler) String() string { | ||||
| 	return "registry" | ||||
| } | ||||
|  | ||||
| func NewHandler(opts ...handler.Option) handler.Handler { | ||||
| 	options := handler.NewOptions(opts...) | ||||
|  | ||||
| 	return ®istryHandler{ | ||||
| 		opts: options, | ||||
| 		reg:  options.Service.Client().Options().Registry, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										307
									
								
								api/handler/rpc/rpc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										307
									
								
								api/handler/rpc/rpc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,307 @@ | ||||
| // Package rpc is a go-micro rpc handler. | ||||
| package rpc | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/joncalhoun/qson" | ||||
| 	"github.com/micro/go-micro/api" | ||||
| 	"github.com/micro/go-micro/api/handler" | ||||
| 	proto "github.com/micro/go-micro/api/internal/proto" | ||||
| 	"github.com/micro/go-micro/client" | ||||
| 	"github.com/micro/go-micro/codec" | ||||
| 	"github.com/micro/go-micro/codec/jsonrpc" | ||||
| 	"github.com/micro/go-micro/codec/protorpc" | ||||
| 	"github.com/micro/go-micro/errors" | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/selector" | ||||
| 	"github.com/micro/go-micro/util/ctx" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	Handler = "rpc" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// supported json codecs | ||||
| 	jsonCodecs = []string{ | ||||
| 		"application/grpc+json", | ||||
| 		"application/json", | ||||
| 		"application/json-rpc", | ||||
| 	} | ||||
|  | ||||
| 	// support proto codecs | ||||
| 	protoCodecs = []string{ | ||||
| 		"application/grpc", | ||||
| 		"application/grpc+proto", | ||||
| 		"application/proto", | ||||
| 		"application/protobuf", | ||||
| 		"application/proto-rpc", | ||||
| 		"application/octet-stream", | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| type rpcHandler struct { | ||||
| 	opts handler.Options | ||||
| 	s    *api.Service | ||||
| } | ||||
|  | ||||
| type buffer struct { | ||||
| 	io.ReadCloser | ||||
| } | ||||
|  | ||||
| func (b *buffer) Write(_ []byte) (int, error) { | ||||
| 	return 0, nil | ||||
| } | ||||
|  | ||||
| // strategy is a hack for selection | ||||
| func strategy(services []*registry.Service) selector.Strategy { | ||||
| 	return func(_ []*registry.Service) selector.Next { | ||||
| 		// ignore input to this function, use services above | ||||
| 		return selector.Random(services) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	defer r.Body.Close() | ||||
| 	var service *api.Service | ||||
|  | ||||
| 	if h.s != nil { | ||||
| 		// we were given the service | ||||
| 		service = h.s | ||||
| 	} else if h.opts.Router != nil { | ||||
| 		// try get service from router | ||||
| 		s, err := h.opts.Router.Route(r) | ||||
| 		if err != nil { | ||||
| 			writeError(w, r, errors.InternalServerError("go.micro.api", err.Error())) | ||||
| 			return | ||||
| 		} | ||||
| 		service = s | ||||
| 	} else { | ||||
| 		// we have no way of routing the request | ||||
| 		writeError(w, r, errors.InternalServerError("go.micro.api", "no route found")) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// only allow post when we have the router | ||||
| 	if r.Method != "GET" && (h.opts.Router != nil && r.Method != "POST") { | ||||
| 		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	ct := r.Header.Get("Content-Type") | ||||
|  | ||||
| 	// Strip charset from Content-Type (like `application/json; charset=UTF-8`) | ||||
| 	if idx := strings.IndexRune(ct, ';'); idx >= 0 { | ||||
| 		ct = ct[:idx] | ||||
| 	} | ||||
|  | ||||
| 	// micro client | ||||
| 	c := h.opts.Service.Client() | ||||
|  | ||||
| 	// create strategy | ||||
| 	so := selector.WithStrategy(strategy(service.Services)) | ||||
|  | ||||
| 	// get payload | ||||
| 	br, err := requestPayload(r) | ||||
| 	if err != nil { | ||||
| 		writeError(w, r, err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// create context | ||||
| 	cx := ctx.FromRequest(r) | ||||
|  | ||||
| 	var rsp []byte | ||||
|  | ||||
| 	switch { | ||||
| 	// json codecs | ||||
| 	case hasCodec(ct, jsonCodecs): | ||||
| 		var request json.RawMessage | ||||
| 		// if the extracted payload isn't empty lets use it | ||||
| 		if len(br) > 0 { | ||||
| 			request = json.RawMessage(br) | ||||
| 		} | ||||
|  | ||||
| 		// create request/response | ||||
| 		var response json.RawMessage | ||||
|  | ||||
| 		req := c.NewRequest( | ||||
| 			service.Name, | ||||
| 			service.Endpoint.Name, | ||||
| 			&request, | ||||
| 			client.WithContentType(ct), | ||||
| 		) | ||||
|  | ||||
| 		// make the call | ||||
| 		if err := c.Call(cx, req, &response, client.WithSelectOption(so)); err != nil { | ||||
| 			writeError(w, r, err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// marshall response | ||||
| 		rsp, _ = response.MarshalJSON() | ||||
| 	// proto codecs | ||||
| 	case hasCodec(ct, protoCodecs): | ||||
| 		request := &proto.Message{} | ||||
| 		// if the extracted payload isn't empty lets use it | ||||
| 		if len(br) > 0 { | ||||
| 			request = proto.NewMessage(br) | ||||
| 		} | ||||
|  | ||||
| 		// create request/response | ||||
| 		response := &proto.Message{} | ||||
|  | ||||
| 		req := c.NewRequest( | ||||
| 			service.Name, | ||||
| 			service.Endpoint.Name, | ||||
| 			request, | ||||
| 			client.WithContentType(ct), | ||||
| 		) | ||||
|  | ||||
| 		// make the call | ||||
| 		if err := c.Call(cx, req, response, client.WithSelectOption(so)); err != nil { | ||||
| 			writeError(w, r, err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// marshall response | ||||
| 		rsp, _ = response.Marshal() | ||||
| 	default: | ||||
| 		http.Error(w, "Unsupported Content-Type", 400) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// write the response | ||||
| 	writeResponse(w, r, rsp) | ||||
| } | ||||
|  | ||||
| func (rh *rpcHandler) String() string { | ||||
| 	return "rpc" | ||||
| } | ||||
|  | ||||
| func hasCodec(ct string, codecs []string) bool { | ||||
| 	for _, codec := range codecs { | ||||
| 		if ct == codec { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // requestPayload takes a *http.Request. | ||||
| // If the request is a GET the query string parameters are extracted and marshaled to JSON and the raw bytes are returned. | ||||
| // If the request method is a POST the request body is read and returned | ||||
| func requestPayload(r *http.Request) ([]byte, error) { | ||||
| 	// we have to decode json-rpc and proto-rpc because we suck | ||||
| 	// well actually because there's no proxy codec right now | ||||
| 	switch r.Header.Get("Content-Type") { | ||||
| 	case "application/json-rpc": | ||||
| 		msg := codec.Message{ | ||||
| 			Type:   codec.Request, | ||||
| 			Header: make(map[string]string), | ||||
| 		} | ||||
| 		c := jsonrpc.NewCodec(&buffer{r.Body}) | ||||
| 		if err := c.ReadHeader(&msg, codec.Request); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		var raw json.RawMessage | ||||
| 		if err := c.ReadBody(&raw); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return ([]byte)(raw), nil | ||||
| 	case "application/proto-rpc", "application/octet-stream": | ||||
| 		msg := codec.Message{ | ||||
| 			Type:   codec.Request, | ||||
| 			Header: make(map[string]string), | ||||
| 		} | ||||
| 		c := protorpc.NewCodec(&buffer{r.Body}) | ||||
| 		if err := c.ReadHeader(&msg, codec.Request); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		var raw proto.Message | ||||
| 		if err := c.ReadBody(&raw); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		b, _ := raw.Marshal() | ||||
| 		return b, nil | ||||
| 	} | ||||
|  | ||||
| 	// otherwise as per usual | ||||
|  | ||||
| 	switch r.Method { | ||||
| 	case "GET": | ||||
| 		if len(r.URL.RawQuery) > 0 { | ||||
| 			return qson.ToJSON(r.URL.RawQuery) | ||||
| 		} | ||||
| 	case "PATCH", "POST": | ||||
| 		return ioutil.ReadAll(r.Body) | ||||
| 	} | ||||
|  | ||||
| 	return []byte{}, nil | ||||
| } | ||||
|  | ||||
| func writeError(w http.ResponseWriter, r *http.Request, err error) { | ||||
| 	ce := errors.Parse(err.Error()) | ||||
|  | ||||
| 	switch ce.Code { | ||||
| 	case 0: | ||||
| 		// assuming it's totally screwed | ||||
| 		ce.Code = 500 | ||||
| 		ce.Id = "go.micro.api" | ||||
| 		ce.Status = http.StatusText(500) | ||||
| 		ce.Detail = "error during request: " + ce.Detail | ||||
| 		w.WriteHeader(500) | ||||
| 	default: | ||||
| 		w.WriteHeader(int(ce.Code)) | ||||
| 	} | ||||
|  | ||||
| 	// response content type | ||||
| 	w.Header().Set("Content-Type", "application/json") | ||||
|  | ||||
| 	// Set trailers | ||||
| 	if strings.Contains(r.Header.Get("Content-Type"), "application/grpc") { | ||||
| 		w.Header().Set("Trailer", "grpc-status") | ||||
| 		w.Header().Set("Trailer", "grpc-message") | ||||
| 		w.Header().Set("grpc-status", "13") | ||||
| 		w.Header().Set("grpc-message", ce.Detail) | ||||
| 	} | ||||
|  | ||||
| 	w.Write([]byte(ce.Error())) | ||||
| } | ||||
|  | ||||
| func writeResponse(w http.ResponseWriter, r *http.Request, rsp []byte) { | ||||
| 	w.Header().Set("Content-Type", r.Header.Get("Content-Type")) | ||||
| 	w.Header().Set("Content-Length", strconv.Itoa(len(rsp))) | ||||
|  | ||||
| 	// Set trailers | ||||
| 	if strings.Contains(r.Header.Get("Content-Type"), "application/grpc") { | ||||
| 		w.Header().Set("Trailer", "grpc-status") | ||||
| 		w.Header().Set("Trailer", "grpc-message") | ||||
| 		w.Header().Set("grpc-status", "0") | ||||
| 		w.Header().Set("grpc-message", "") | ||||
| 	} | ||||
|  | ||||
| 	// write response | ||||
| 	w.Write(rsp) | ||||
| } | ||||
|  | ||||
| func NewHandler(opts ...handler.Option) handler.Handler { | ||||
| 	options := handler.NewOptions(opts...) | ||||
| 	return &rpcHandler{ | ||||
| 		opts: options, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func WithService(s *api.Service, opts ...handler.Option) handler.Handler { | ||||
| 	options := handler.NewOptions(opts...) | ||||
| 	return &rpcHandler{ | ||||
| 		opts: options, | ||||
| 		s:    s, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										95
									
								
								api/handler/rpc/rpc_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								api/handler/rpc/rpc_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| package rpc | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"net/http" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/golang/protobuf/proto" | ||||
| 	"github.com/micro/go-micro/api/proto" | ||||
| ) | ||||
|  | ||||
| func TestRequestPayloadFromRequest(t *testing.T) { | ||||
|  | ||||
| 	// our test event so that we can validate serialising / deserializing of true protos works | ||||
| 	protoEvent := go_api.Event{ | ||||
| 		Name: "Test", | ||||
| 	} | ||||
|  | ||||
| 	protoBytes, err := proto.Marshal(&protoEvent) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Failed to marshal proto", err) | ||||
| 	} | ||||
|  | ||||
| 	jsonBytes, err := json.Marshal(protoEvent) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Failed to marshal proto to JSON ", err) | ||||
| 	} | ||||
|  | ||||
| 	t.Run("extracting a proto from a POST request", func(t *testing.T) { | ||||
| 		r, err := http.NewRequest("POST", "http://localhost/my/path", bytes.NewReader(protoBytes)) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Failed to created http.Request: %v", err) | ||||
| 		} | ||||
|  | ||||
| 		extByte, err := requestPayload(r) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Failed to extract payload from request: %v", err) | ||||
| 		} | ||||
| 		if string(extByte) != string(protoBytes) { | ||||
| 			t.Fatalf("Expected %v and %v to match", string(extByte), string(protoBytes)) | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("extracting JSON from a POST request", func(t *testing.T) { | ||||
| 		r, err := http.NewRequest("POST", "http://localhost/my/path", bytes.NewReader(jsonBytes)) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Failed to created http.Request: %v", err) | ||||
| 		} | ||||
|  | ||||
| 		extByte, err := requestPayload(r) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Failed to extract payload from request: %v", err) | ||||
| 		} | ||||
| 		if string(extByte) != string(jsonBytes) { | ||||
| 			t.Fatalf("Expected %v and %v to match", string(extByte), string(jsonBytes)) | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("extracting params from a GET request", func(t *testing.T) { | ||||
|  | ||||
| 		r, err := http.NewRequest("GET", "http://localhost/my/path", nil) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Failed to created http.Request: %v", err) | ||||
| 		} | ||||
|  | ||||
| 		q := r.URL.Query() | ||||
| 		q.Add("name", "Test") | ||||
| 		r.URL.RawQuery = q.Encode() | ||||
|  | ||||
| 		extByte, err := requestPayload(r) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Failed to extract payload from request: %v", err) | ||||
| 		} | ||||
| 		if string(extByte) != string(jsonBytes) { | ||||
| 			t.Fatalf("Expected %v and %v to match", string(extByte), string(jsonBytes)) | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("GET request with no params", func(t *testing.T) { | ||||
|  | ||||
| 		r, err := http.NewRequest("GET", "http://localhost/my/path", nil) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Failed to created http.Request: %v", err) | ||||
| 		} | ||||
|  | ||||
| 		extByte, err := requestPayload(r) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Failed to extract payload from request: %v", err) | ||||
| 		} | ||||
| 		if string(extByte) != "" { | ||||
| 			t.Fatalf("Expected %v and %v to match", string(extByte), "") | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										25
									
								
								api/handler/udp/udp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								api/handler/udp/udp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| // Package udp reads and write from a udp connection | ||||
| package udp | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| type Handler struct{} | ||||
|  | ||||
| func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	c, err := net.Dial("udp", r.Host) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
| 	go io.Copy(c, r.Body) | ||||
| 	// write response | ||||
| 	io.Copy(w, c) | ||||
| } | ||||
|  | ||||
| func (h *Handler) String() string { | ||||
| 	return "udp" | ||||
| } | ||||
							
								
								
									
										30
									
								
								api/handler/unix/unix.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								api/handler/unix/unix.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| // Package unix reads from a unix socket expecting it to be in /tmp/path | ||||
| package unix | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"path/filepath" | ||||
| ) | ||||
|  | ||||
| type Handler struct{} | ||||
|  | ||||
| func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	sock := fmt.Sprintf("%s.sock", filepath.Clean(r.URL.Path)) | ||||
| 	path := filepath.Join("/tmp", sock) | ||||
|  | ||||
| 	c, err := net.Dial("unix", path) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
| 	go io.Copy(c, r.Body) | ||||
| 	// write response | ||||
| 	io.Copy(w, c) | ||||
| } | ||||
|  | ||||
| func (h *Handler) String() string { | ||||
| 	return "unix" | ||||
| } | ||||
							
								
								
									
										177
									
								
								api/handler/web/web.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								api/handler/web/web.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | ||||
| // Package web contains the web handler including websocket support | ||||
| package web | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/http/httputil" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/micro/go-micro/api" | ||||
| 	"github.com/micro/go-micro/api/handler" | ||||
| 	"github.com/micro/go-micro/selector" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	Handler = "web" | ||||
| ) | ||||
|  | ||||
| type webHandler struct { | ||||
| 	opts handler.Options | ||||
| 	s    *api.Service | ||||
| } | ||||
|  | ||||
| func (wh *webHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	service, err := wh.getService(r) | ||||
| 	if err != nil { | ||||
| 		w.WriteHeader(500) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if len(service) == 0 { | ||||
| 		w.WriteHeader(404) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	rp, err := url.Parse(service) | ||||
| 	if err != nil { | ||||
| 		w.WriteHeader(500) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if isWebSocket(r) { | ||||
| 		wh.serveWebSocket(rp.Host, w, r) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	httputil.NewSingleHostReverseProxy(rp).ServeHTTP(w, r) | ||||
| } | ||||
|  | ||||
| // getService returns the service for this request from the selector | ||||
| func (wh *webHandler) getService(r *http.Request) (string, error) { | ||||
| 	var service *api.Service | ||||
|  | ||||
| 	if wh.s != nil { | ||||
| 		// we were given the service | ||||
| 		service = wh.s | ||||
| 	} else if wh.opts.Router != nil { | ||||
| 		// try get service from router | ||||
| 		s, err := wh.opts.Router.Route(r) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		service = s | ||||
| 	} else { | ||||
| 		// we have no way of routing the request | ||||
| 		return "", errors.New("no route found") | ||||
| 	} | ||||
|  | ||||
| 	// create a random selector | ||||
| 	next := selector.Random(service.Services) | ||||
|  | ||||
| 	// get the next node | ||||
| 	s, err := next() | ||||
| 	if err != nil { | ||||
| 		return "", nil | ||||
| 	} | ||||
|  | ||||
| 	return fmt.Sprintf("http://%s:%d", s.Address, s.Port), nil | ||||
| } | ||||
|  | ||||
| // serveWebSocket used to serve a web socket proxied connection | ||||
| func (wh *webHandler) serveWebSocket(host string, w http.ResponseWriter, r *http.Request) { | ||||
| 	req := new(http.Request) | ||||
| 	*req = *r | ||||
|  | ||||
| 	if len(host) == 0 { | ||||
| 		http.Error(w, "invalid host", 500) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// set x-forward-for | ||||
| 	if clientIP, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { | ||||
| 		if ips, ok := req.Header["X-Forwarded-For"]; ok { | ||||
| 			clientIP = strings.Join(ips, ", ") + ", " + clientIP | ||||
| 		} | ||||
| 		req.Header.Set("X-Forwarded-For", clientIP) | ||||
| 	} | ||||
|  | ||||
| 	// connect to the backend host | ||||
| 	conn, err := net.Dial("tcp", host) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// hijack the connection | ||||
| 	hj, ok := w.(http.Hijacker) | ||||
| 	if !ok { | ||||
| 		http.Error(w, "failed to connect", 500) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	nc, _, err := hj.Hijack() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	defer nc.Close() | ||||
| 	defer conn.Close() | ||||
|  | ||||
| 	if err = req.Write(conn); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	errCh := make(chan error, 2) | ||||
|  | ||||
| 	cp := func(dst io.Writer, src io.Reader) { | ||||
| 		_, err := io.Copy(dst, src) | ||||
| 		errCh <- err | ||||
| 	} | ||||
|  | ||||
| 	go cp(conn, nc) | ||||
| 	go cp(nc, conn) | ||||
|  | ||||
| 	<-errCh | ||||
| } | ||||
|  | ||||
| func isWebSocket(r *http.Request) bool { | ||||
| 	contains := func(key, val string) bool { | ||||
| 		vv := strings.Split(r.Header.Get(key), ",") | ||||
| 		for _, v := range vv { | ||||
| 			if val == strings.ToLower(strings.TrimSpace(v)) { | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if contains("Connection", "upgrade") && contains("Upgrade", "websocket") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (wh *webHandler) String() string { | ||||
| 	return "web" | ||||
| } | ||||
|  | ||||
| func NewHandler(opts ...handler.Option) handler.Handler { | ||||
| 	return &webHandler{ | ||||
| 		opts: handler.NewOptions(opts...), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func WithService(s *api.Service, opts ...handler.Option) handler.Handler { | ||||
| 	options := handler.NewOptions(opts...) | ||||
|  | ||||
| 	return &webHandler{ | ||||
| 		opts: options, | ||||
| 		s:    s, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										28
									
								
								api/internal/proto/message.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								api/internal/proto/message.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| package proto | ||||
|  | ||||
| type Message struct { | ||||
| 	data []byte | ||||
| } | ||||
|  | ||||
| func (m *Message) ProtoMessage() {} | ||||
|  | ||||
| func (m *Message) Reset() { | ||||
| 	*m = Message{} | ||||
| } | ||||
|  | ||||
| func (m *Message) String() string { | ||||
| 	return string(m.data) | ||||
| } | ||||
|  | ||||
| func (m *Message) Marshal() ([]byte, error) { | ||||
| 	return m.data, nil | ||||
| } | ||||
|  | ||||
| func (m *Message) Unmarshal(data []byte) error { | ||||
| 	m.data = data | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func NewMessage(data []byte) *Message { | ||||
| 	return &Message{data} | ||||
| } | ||||
							
								
								
									
										31
									
								
								api/proto/api.micro.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								api/proto/api.micro.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| // Code generated by protoc-gen-micro. DO NOT EDIT. | ||||
| // source: github.com/micro/go-micro/api/proto/api.proto | ||||
|  | ||||
| /* | ||||
| Package go_api is a generated protocol buffer package. | ||||
|  | ||||
| It is generated from these files: | ||||
| 	github.com/micro/go-micro/api/proto/api.proto | ||||
|  | ||||
| It has these top-level messages: | ||||
| 	Pair | ||||
| 	Request | ||||
| 	Response | ||||
| 	Event | ||||
| */ | ||||
| package go_api | ||||
|  | ||||
| import proto "github.com/golang/protobuf/proto" | ||||
| import fmt "fmt" | ||||
| import math "math" | ||||
|  | ||||
| // Reference imports to suppress errors if they are not otherwise used. | ||||
| var _ = proto.Marshal | ||||
| var _ = fmt.Errorf | ||||
| var _ = math.Inf | ||||
|  | ||||
| // This is a compile-time assertion to ensure that this generated file | ||||
| // is compatible with the proto package it is being compiled against. | ||||
| // A compilation error at this line likely means your copy of the | ||||
| // proto package needs to be updated. | ||||
| const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package | ||||
							
								
								
									
										332
									
								
								api/proto/api.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										332
									
								
								api/proto/api.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,332 @@ | ||||
| // Code generated by protoc-gen-go. DO NOT EDIT. | ||||
| // source: github.com/micro/go-micro/api/proto/api.proto | ||||
|  | ||||
| package go_api | ||||
|  | ||||
| import proto "github.com/golang/protobuf/proto" | ||||
| import fmt "fmt" | ||||
| import math "math" | ||||
|  | ||||
| // Reference imports to suppress errors if they are not otherwise used. | ||||
| var _ = proto.Marshal | ||||
| var _ = fmt.Errorf | ||||
| var _ = math.Inf | ||||
|  | ||||
| // This is a compile-time assertion to ensure that this generated file | ||||
| // is compatible with the proto package it is being compiled against. | ||||
| // A compilation error at this line likely means your copy of the | ||||
| // proto package needs to be updated. | ||||
| const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package | ||||
|  | ||||
| type Pair struct { | ||||
| 	Key                  string   `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` | ||||
| 	Values               []string `protobuf:"bytes,2,rep,name=values,proto3" json:"values,omitempty"` | ||||
| 	XXX_NoUnkeyedLiteral struct{} `json:"-"` | ||||
| 	XXX_unrecognized     []byte   `json:"-"` | ||||
| 	XXX_sizecache        int32    `json:"-"` | ||||
| } | ||||
|  | ||||
| func (m *Pair) Reset()         { *m = Pair{} } | ||||
| func (m *Pair) String() string { return proto.CompactTextString(m) } | ||||
| func (*Pair) ProtoMessage()    {} | ||||
| func (*Pair) Descriptor() ([]byte, []int) { | ||||
| 	return fileDescriptor_api_17a7876430d97ebd, []int{0} | ||||
| } | ||||
| func (m *Pair) XXX_Unmarshal(b []byte) error { | ||||
| 	return xxx_messageInfo_Pair.Unmarshal(m, b) | ||||
| } | ||||
| func (m *Pair) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { | ||||
| 	return xxx_messageInfo_Pair.Marshal(b, m, deterministic) | ||||
| } | ||||
| func (dst *Pair) XXX_Merge(src proto.Message) { | ||||
| 	xxx_messageInfo_Pair.Merge(dst, src) | ||||
| } | ||||
| func (m *Pair) XXX_Size() int { | ||||
| 	return xxx_messageInfo_Pair.Size(m) | ||||
| } | ||||
| func (m *Pair) XXX_DiscardUnknown() { | ||||
| 	xxx_messageInfo_Pair.DiscardUnknown(m) | ||||
| } | ||||
|  | ||||
| var xxx_messageInfo_Pair proto.InternalMessageInfo | ||||
|  | ||||
| func (m *Pair) GetKey() string { | ||||
| 	if m != nil { | ||||
| 		return m.Key | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (m *Pair) GetValues() []string { | ||||
| 	if m != nil { | ||||
| 		return m.Values | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // A HTTP request as RPC | ||||
| // Forward by the api handler | ||||
| type Request struct { | ||||
| 	Method               string           `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` | ||||
| 	Path                 string           `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` | ||||
| 	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"` | ||||
| 	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"` | ||||
| 	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"` | ||||
| 	Body                 string           `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` | ||||
| 	Url                  string           `protobuf:"bytes,7,opt,name=url,proto3" json:"url,omitempty"` | ||||
| 	XXX_NoUnkeyedLiteral struct{}         `json:"-"` | ||||
| 	XXX_unrecognized     []byte           `json:"-"` | ||||
| 	XXX_sizecache        int32            `json:"-"` | ||||
| } | ||||
|  | ||||
| func (m *Request) Reset()         { *m = Request{} } | ||||
| func (m *Request) String() string { return proto.CompactTextString(m) } | ||||
| func (*Request) ProtoMessage()    {} | ||||
| func (*Request) Descriptor() ([]byte, []int) { | ||||
| 	return fileDescriptor_api_17a7876430d97ebd, []int{1} | ||||
| } | ||||
| func (m *Request) XXX_Unmarshal(b []byte) error { | ||||
| 	return xxx_messageInfo_Request.Unmarshal(m, b) | ||||
| } | ||||
| func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { | ||||
| 	return xxx_messageInfo_Request.Marshal(b, m, deterministic) | ||||
| } | ||||
| func (dst *Request) XXX_Merge(src proto.Message) { | ||||
| 	xxx_messageInfo_Request.Merge(dst, src) | ||||
| } | ||||
| func (m *Request) XXX_Size() int { | ||||
| 	return xxx_messageInfo_Request.Size(m) | ||||
| } | ||||
| func (m *Request) XXX_DiscardUnknown() { | ||||
| 	xxx_messageInfo_Request.DiscardUnknown(m) | ||||
| } | ||||
|  | ||||
| var xxx_messageInfo_Request proto.InternalMessageInfo | ||||
|  | ||||
| func (m *Request) GetMethod() string { | ||||
| 	if m != nil { | ||||
| 		return m.Method | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (m *Request) GetPath() string { | ||||
| 	if m != nil { | ||||
| 		return m.Path | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (m *Request) GetHeader() map[string]*Pair { | ||||
| 	if m != nil { | ||||
| 		return m.Header | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *Request) GetGet() map[string]*Pair { | ||||
| 	if m != nil { | ||||
| 		return m.Get | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *Request) GetPost() map[string]*Pair { | ||||
| 	if m != nil { | ||||
| 		return m.Post | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *Request) GetBody() string { | ||||
| 	if m != nil { | ||||
| 		return m.Body | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (m *Request) GetUrl() string { | ||||
| 	if m != nil { | ||||
| 		return m.Url | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // A HTTP response as RPC | ||||
| // Expected response for the api handler | ||||
| type Response struct { | ||||
| 	StatusCode           int32            `protobuf:"varint,1,opt,name=statusCode,proto3" json:"statusCode,omitempty"` | ||||
| 	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"` | ||||
| 	Body                 string           `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"` | ||||
| 	XXX_NoUnkeyedLiteral struct{}         `json:"-"` | ||||
| 	XXX_unrecognized     []byte           `json:"-"` | ||||
| 	XXX_sizecache        int32            `json:"-"` | ||||
| } | ||||
|  | ||||
| func (m *Response) Reset()         { *m = Response{} } | ||||
| func (m *Response) String() string { return proto.CompactTextString(m) } | ||||
| func (*Response) ProtoMessage()    {} | ||||
| func (*Response) Descriptor() ([]byte, []int) { | ||||
| 	return fileDescriptor_api_17a7876430d97ebd, []int{2} | ||||
| } | ||||
| func (m *Response) XXX_Unmarshal(b []byte) error { | ||||
| 	return xxx_messageInfo_Response.Unmarshal(m, b) | ||||
| } | ||||
| func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { | ||||
| 	return xxx_messageInfo_Response.Marshal(b, m, deterministic) | ||||
| } | ||||
| func (dst *Response) XXX_Merge(src proto.Message) { | ||||
| 	xxx_messageInfo_Response.Merge(dst, src) | ||||
| } | ||||
| func (m *Response) XXX_Size() int { | ||||
| 	return xxx_messageInfo_Response.Size(m) | ||||
| } | ||||
| func (m *Response) XXX_DiscardUnknown() { | ||||
| 	xxx_messageInfo_Response.DiscardUnknown(m) | ||||
| } | ||||
|  | ||||
| var xxx_messageInfo_Response proto.InternalMessageInfo | ||||
|  | ||||
| func (m *Response) GetStatusCode() int32 { | ||||
| 	if m != nil { | ||||
| 		return m.StatusCode | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func (m *Response) GetHeader() map[string]*Pair { | ||||
| 	if m != nil { | ||||
| 		return m.Header | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *Response) GetBody() string { | ||||
| 	if m != nil { | ||||
| 		return m.Body | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // A HTTP event as RPC | ||||
| // Forwarded by the event handler | ||||
| type Event struct { | ||||
| 	// e.g login | ||||
| 	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` | ||||
| 	// uuid | ||||
| 	Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` | ||||
| 	// unix timestamp of event | ||||
| 	Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` | ||||
| 	// event headers | ||||
| 	Header map[string]*Pair `protobuf:"bytes,4,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` | ||||
| 	// the event data | ||||
| 	Data                 string   `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` | ||||
| 	XXX_NoUnkeyedLiteral struct{} `json:"-"` | ||||
| 	XXX_unrecognized     []byte   `json:"-"` | ||||
| 	XXX_sizecache        int32    `json:"-"` | ||||
| } | ||||
|  | ||||
| func (m *Event) Reset()         { *m = Event{} } | ||||
| func (m *Event) String() string { return proto.CompactTextString(m) } | ||||
| func (*Event) ProtoMessage()    {} | ||||
| func (*Event) Descriptor() ([]byte, []int) { | ||||
| 	return fileDescriptor_api_17a7876430d97ebd, []int{3} | ||||
| } | ||||
| func (m *Event) XXX_Unmarshal(b []byte) error { | ||||
| 	return xxx_messageInfo_Event.Unmarshal(m, b) | ||||
| } | ||||
| func (m *Event) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { | ||||
| 	return xxx_messageInfo_Event.Marshal(b, m, deterministic) | ||||
| } | ||||
| func (dst *Event) XXX_Merge(src proto.Message) { | ||||
| 	xxx_messageInfo_Event.Merge(dst, src) | ||||
| } | ||||
| func (m *Event) XXX_Size() int { | ||||
| 	return xxx_messageInfo_Event.Size(m) | ||||
| } | ||||
| func (m *Event) XXX_DiscardUnknown() { | ||||
| 	xxx_messageInfo_Event.DiscardUnknown(m) | ||||
| } | ||||
|  | ||||
| var xxx_messageInfo_Event proto.InternalMessageInfo | ||||
|  | ||||
| func (m *Event) GetName() string { | ||||
| 	if m != nil { | ||||
| 		return m.Name | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (m *Event) GetId() string { | ||||
| 	if m != nil { | ||||
| 		return m.Id | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (m *Event) GetTimestamp() int64 { | ||||
| 	if m != nil { | ||||
| 		return m.Timestamp | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func (m *Event) GetHeader() map[string]*Pair { | ||||
| 	if m != nil { | ||||
| 		return m.Header | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *Event) GetData() string { | ||||
| 	if m != nil { | ||||
| 		return m.Data | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	proto.RegisterType((*Pair)(nil), "go.api.Pair") | ||||
| 	proto.RegisterType((*Request)(nil), "go.api.Request") | ||||
| 	proto.RegisterMapType((map[string]*Pair)(nil), "go.api.Request.GetEntry") | ||||
| 	proto.RegisterMapType((map[string]*Pair)(nil), "go.api.Request.HeaderEntry") | ||||
| 	proto.RegisterMapType((map[string]*Pair)(nil), "go.api.Request.PostEntry") | ||||
| 	proto.RegisterType((*Response)(nil), "go.api.Response") | ||||
| 	proto.RegisterMapType((map[string]*Pair)(nil), "go.api.Response.HeaderEntry") | ||||
| 	proto.RegisterType((*Event)(nil), "go.api.Event") | ||||
| 	proto.RegisterMapType((map[string]*Pair)(nil), "go.api.Event.HeaderEntry") | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	proto.RegisterFile("github.com/micro/go-micro/api/proto/api.proto", fileDescriptor_api_17a7876430d97ebd) | ||||
| } | ||||
|  | ||||
| var fileDescriptor_api_17a7876430d97ebd = []byte{ | ||||
| 	// 410 bytes of a gzipped FileDescriptorProto | ||||
| 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x53, 0xc1, 0x6e, 0xd4, 0x30, | ||||
| 	0x10, 0x55, 0xe2, 0x24, 0x6d, 0x66, 0x11, 0x42, 0x3e, 0x20, 0x53, 0x2a, 0xb4, 0xca, 0x85, 0x15, | ||||
| 	0x52, 0x13, 0x68, 0x39, 0x20, 0xae, 0xb0, 0x2a, 0xc7, 0xca, 0x7f, 0xe0, 0x6d, 0xac, 0xc4, 0x62, | ||||
| 	0x13, 0x9b, 0xd8, 0xa9, 0xb4, 0x1f, 0xc7, 0x81, 0xcf, 0xe0, 0x6f, 0x90, 0x27, 0xde, 0xdd, 0xb2, | ||||
| 	0x5a, 0x2e, 0x74, 0x6f, 0x2f, 0xf6, 0x9b, 0x37, 0x6f, 0xde, 0x38, 0xf0, 0xb6, 0x51, 0xae, 0x1d, | ||||
| 	0x57, 0xe5, 0xbd, 0xee, 0xaa, 0x4e, 0xdd, 0x0f, 0xba, 0x6a, 0xf4, 0x95, 0x30, 0xaa, 0x32, 0x83, | ||||
| 	0x76, 0xba, 0x12, 0x46, 0x95, 0x88, 0x68, 0xd6, 0xe8, 0x52, 0x18, 0x55, 0xbc, 0x87, 0xe4, 0x4e, | ||||
| 	0xa8, 0x81, 0xbe, 0x00, 0xf2, 0x5d, 0x6e, 0x58, 0x34, 0x8f, 0x16, 0x39, 0xf7, 0x90, 0xbe, 0x84, | ||||
| 	0xec, 0x41, 0xac, 0x47, 0x69, 0x59, 0x3c, 0x27, 0x8b, 0x9c, 0x87, 0xaf, 0xe2, 0x17, 0x81, 0x33, | ||||
| 	0x2e, 0x7f, 0x8c, 0xd2, 0x3a, 0xcf, 0xe9, 0xa4, 0x6b, 0x75, 0x1d, 0x0a, 0xc3, 0x17, 0xa5, 0x90, | ||||
| 	0x18, 0xe1, 0x5a, 0x16, 0xe3, 0x29, 0x62, 0x7a, 0x03, 0x59, 0x2b, 0x45, 0x2d, 0x07, 0x46, 0xe6, | ||||
| 	0x64, 0x31, 0xbb, 0x7e, 0x5d, 0x4e, 0x16, 0xca, 0x20, 0x56, 0x7e, 0xc3, 0xdb, 0x65, 0xef, 0x86, | ||||
| 	0x0d, 0x0f, 0x54, 0xfa, 0x0e, 0x48, 0x23, 0x1d, 0x4b, 0xb0, 0x82, 0x1d, 0x56, 0xdc, 0x4a, 0x37, | ||||
| 	0xd1, 0x3d, 0x89, 0x5e, 0x41, 0x62, 0xb4, 0x75, 0x2c, 0x45, 0xf2, 0xab, 0x43, 0xf2, 0x9d, 0xb6, | ||||
| 	0x81, 0x8d, 0x34, 0xef, 0x71, 0xa5, 0xeb, 0x0d, 0xcb, 0x26, 0x8f, 0x1e, 0xfb, 0x14, 0xc6, 0x61, | ||||
| 	0xcd, 0xce, 0xa6, 0x14, 0xc6, 0x61, 0x7d, 0x71, 0x0b, 0xb3, 0x47, 0xbe, 0x8e, 0xc4, 0x54, 0x40, | ||||
| 	0x8a, 0xc1, 0xe0, 0xac, 0xb3, 0xeb, 0x67, 0xdb, 0xb6, 0x3e, 0x55, 0x3e, 0x5d, 0x7d, 0x8e, 0x3f, | ||||
| 	0x45, 0x17, 0x5f, 0xe1, 0x7c, 0x6b, 0xf7, 0x09, 0x2a, 0x4b, 0xc8, 0x77, 0x73, 0xfc, 0xbf, 0x4c, | ||||
| 	0xf1, 0x33, 0x82, 0x73, 0x2e, 0xad, 0xd1, 0xbd, 0x95, 0xf4, 0x0d, 0x80, 0x75, 0xc2, 0x8d, 0xf6, | ||||
| 	0x8b, 0xae, 0x25, 0xaa, 0xa5, 0xfc, 0xd1, 0x09, 0xfd, 0xb8, 0x5b, 0x5c, 0x8c, 0xc9, 0x5e, 0xee, | ||||
| 	0x93, 0x9d, 0x14, 0x8e, 0x6e, 0x6e, 0x1b, 0x2f, 0xd9, 0xc7, 0x7b, 0xb2, 0x30, 0x8b, 0xdf, 0x11, | ||||
| 	0xa4, 0xcb, 0x07, 0xd9, 0xe3, 0x16, 0x7b, 0xd1, 0xc9, 0x20, 0x82, 0x98, 0x3e, 0x87, 0x58, 0xd5, | ||||
| 	0xe1, 0xed, 0xc5, 0xaa, 0xa6, 0x97, 0x90, 0x3b, 0xd5, 0x49, 0xeb, 0x44, 0x67, 0xd0, 0x0f, 0xe1, | ||||
| 	0xfb, 0x03, 0xfa, 0x61, 0x37, 0x5e, 0xf2, 0xf7, 0xc3, 0xc1, 0x06, 0xff, 0x9a, 0xad, 0x16, 0x4e, | ||||
| 	0xb0, 0x74, 0x6a, 0xea, 0xf1, 0xc9, 0x66, 0x5b, 0x65, 0xf8, 0x83, 0xde, 0xfc, 0x09, 0x00, 0x00, | ||||
| 	0xff, 0xff, 0x7a, 0xb4, 0xd4, 0x8f, 0xcb, 0x03, 0x00, 0x00, | ||||
| } | ||||
							
								
								
									
										43
									
								
								api/proto/api.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								api/proto/api.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| syntax = "proto3"; | ||||
|  | ||||
| package go.api; | ||||
|  | ||||
| message Pair { | ||||
| 	string key = 1; | ||||
| 	repeated string values = 2; | ||||
| } | ||||
|  | ||||
| // A HTTP request as RPC | ||||
| // Forward by the api handler | ||||
| message Request { | ||||
|         string method = 1; | ||||
|         string path = 2; | ||||
|         map<string, Pair> header = 3; | ||||
|         map<string, Pair> get = 4; | ||||
|         map<string, Pair> post = 5; | ||||
|         string body = 6;  // raw request body; if not application/x-www-form-urlencoded | ||||
| 	string url = 7; | ||||
| } | ||||
|  | ||||
| // A HTTP response as RPC | ||||
| // Expected response for the api handler | ||||
| message Response { | ||||
|         int32 statusCode = 1; | ||||
|         map<string, Pair> header = 2; | ||||
|         string body = 3; | ||||
| } | ||||
|  | ||||
| // A HTTP event as RPC | ||||
| // Forwarded by the event handler | ||||
| message Event { | ||||
| 	// e.g login | ||||
| 	string name = 1; | ||||
| 	// uuid | ||||
| 	string id = 2; | ||||
| 	// unix timestamp of event | ||||
| 	int64 timestamp = 3; | ||||
| 	// event headers | ||||
|         map<string, Pair> header = 4; | ||||
| 	// the event data | ||||
| 	string data = 5; | ||||
| } | ||||
							
								
								
									
										38
									
								
								api/resolver/grpc/grpc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								api/resolver/grpc/grpc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| // Package grpc resolves a grpc service like /greeter.Say/Hello to greeter service | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/micro/go-micro/api/resolver" | ||||
| ) | ||||
|  | ||||
| type Resolver struct{} | ||||
|  | ||||
| func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { | ||||
| 	// /foo.Bar/Service | ||||
| 	if req.URL.Path == "/" { | ||||
| 		return nil, errors.New("unknown name") | ||||
| 	} | ||||
| 	// [foo.Bar, Service] | ||||
| 	parts := strings.Split(req.URL.Path[1:], "/") | ||||
| 	// [foo, Bar] | ||||
| 	name := strings.Split(parts[0], ".") | ||||
| 	// foo | ||||
| 	return &resolver.Endpoint{ | ||||
| 		Name:   strings.Join(name[:len(name)-1], "."), | ||||
| 		Host:   req.Host, | ||||
| 		Method: req.Method, | ||||
| 		Path:   req.URL.Path, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (r *Resolver) String() string { | ||||
| 	return "grpc" | ||||
| } | ||||
|  | ||||
| func NewResolver(opts ...resolver.Option) resolver.Resolver { | ||||
| 	return &Resolver{} | ||||
| } | ||||
							
								
								
									
										27
									
								
								api/resolver/host/host.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								api/resolver/host/host.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| // Package host resolves using http host | ||||
| package host | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/micro/go-micro/api/resolver" | ||||
| ) | ||||
|  | ||||
| type Resolver struct{} | ||||
|  | ||||
| func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { | ||||
| 	return &resolver.Endpoint{ | ||||
| 		Name:   req.Host, | ||||
| 		Host:   req.Host, | ||||
| 		Method: req.Method, | ||||
| 		Path:   req.URL.Path, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (r *Resolver) String() string { | ||||
| 	return "host" | ||||
| } | ||||
|  | ||||
| func NewResolver(opts ...resolver.Option) resolver.Resolver { | ||||
| 	return &Resolver{} | ||||
| } | ||||
							
								
								
									
										45
									
								
								api/resolver/micro/micro.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								api/resolver/micro/micro.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| // Package micro provides a micro rpc resolver which prefixes a namespace | ||||
| package micro | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/micro/go-micro/api/resolver" | ||||
| ) | ||||
|  | ||||
| // default resolver for legacy purposes | ||||
| // it uses proxy routing to resolve names | ||||
| // /foo becomes namespace.foo | ||||
| // /v1/foo becomes namespace.v1.foo | ||||
| type Resolver struct { | ||||
| 	Options resolver.Options | ||||
| } | ||||
|  | ||||
| func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { | ||||
| 	var name, method string | ||||
|  | ||||
| 	switch r.Options.Handler { | ||||
| 	// internal handlers | ||||
| 	case "meta", "api", "rpc", "micro": | ||||
| 		name, method = apiRoute(req.URL.Path) | ||||
| 	default: | ||||
| 		method = req.Method | ||||
| 		name = proxyRoute(req.URL.Path) | ||||
| 	} | ||||
|  | ||||
| 	return &resolver.Endpoint{ | ||||
| 		Name:   name, | ||||
| 		Method: method, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (r *Resolver) String() string { | ||||
| 	return "micro" | ||||
| } | ||||
|  | ||||
| // NewResolver creates a new micro resolver | ||||
| func NewResolver(opts ...resolver.Option) resolver.Resolver { | ||||
| 	return &Resolver{ | ||||
| 		Options: resolver.NewOptions(opts...), | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										90
									
								
								api/resolver/micro/route.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								api/resolver/micro/route.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| package micro | ||||
|  | ||||
| import ( | ||||
| 	"path" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	proxyRe   = regexp.MustCompile("^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$") | ||||
| 	versionRe = regexp.MustCompilePOSIX("^v[0-9]+$") | ||||
| ) | ||||
|  | ||||
| // Translates /foo/bar/zool into api service go.micro.api.foo method Bar.Zool | ||||
| // Translates /foo/bar into api service go.micro.api.foo method Foo.Bar | ||||
| func apiRoute(p string) (string, string) { | ||||
| 	p = path.Clean(p) | ||||
| 	p = strings.TrimPrefix(p, "/") | ||||
| 	parts := strings.Split(p, "/") | ||||
|  | ||||
| 	// If we've got two or less parts | ||||
| 	// Use first part as service | ||||
| 	// Use all parts as method | ||||
| 	if len(parts) <= 2 { | ||||
| 		name := parts[0] | ||||
| 		return name, methodName(parts) | ||||
| 	} | ||||
|  | ||||
| 	// Treat /v[0-9]+ as versioning where we have 3 parts | ||||
| 	// /v1/foo/bar => service: v1.foo method: Foo.bar | ||||
| 	if len(parts) == 3 && versionRe.Match([]byte(parts[0])) { | ||||
| 		name := strings.Join(parts[:len(parts)-1], ".") | ||||
| 		return name, methodName(parts[len(parts)-2:]) | ||||
| 	} | ||||
|  | ||||
| 	// Service is everything minus last two parts | ||||
| 	// Method is the last two parts | ||||
| 	name := strings.Join(parts[:len(parts)-2], ".") | ||||
| 	return name, methodName(parts[len(parts)-2:]) | ||||
| } | ||||
|  | ||||
| func proxyRoute(p string) string { | ||||
| 	parts := strings.Split(p, "/") | ||||
| 	if len(parts) < 2 { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	var service string | ||||
| 	var alias string | ||||
|  | ||||
| 	// /[service]/methods | ||||
| 	if len(parts) > 2 { | ||||
| 		// /v1/[service] | ||||
| 		if versionRe.MatchString(parts[1]) { | ||||
| 			service = parts[1] + "." + parts[2] | ||||
| 			alias = parts[2] | ||||
| 		} else { | ||||
| 			service = parts[1] | ||||
| 			alias = parts[1] | ||||
| 		} | ||||
| 		// /[service] | ||||
| 	} else { | ||||
| 		service = parts[1] | ||||
| 		alias = parts[1] | ||||
| 	} | ||||
|  | ||||
| 	// check service name is valid | ||||
| 	if !proxyRe.MatchString(alias) { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return service | ||||
| } | ||||
|  | ||||
| func methodName(parts []string) string { | ||||
| 	for i, part := range parts { | ||||
| 		parts[i] = toCamel(part) | ||||
| 	} | ||||
|  | ||||
| 	return strings.Join(parts, ".") | ||||
| } | ||||
|  | ||||
| func toCamel(s string) string { | ||||
| 	words := strings.Split(s, "-") | ||||
| 	var out string | ||||
| 	for _, word := range words { | ||||
| 		out += strings.Title(word) | ||||
| 	} | ||||
| 	return out | ||||
| } | ||||
							
								
								
									
										130
									
								
								api/resolver/micro/route_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								api/resolver/micro/route_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| package micro | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestApiRoute(t *testing.T) { | ||||
| 	testData := []struct { | ||||
| 		path    string | ||||
| 		service string | ||||
| 		method  string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			"/foo/bar", | ||||
| 			"foo", | ||||
| 			"Foo.Bar", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/foo/bar", | ||||
| 			"foo", | ||||
| 			"Foo.Bar", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/bar/baz", | ||||
| 			"foo", | ||||
| 			"Bar.Baz", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/bar/baz-xyz", | ||||
| 			"foo", | ||||
| 			"Bar.BazXyz", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/bar/baz/cat", | ||||
| 			"foo.bar", | ||||
| 			"Baz.Cat", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/bar/baz/cat/car", | ||||
| 			"foo.bar.baz", | ||||
| 			"Cat.Car", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/fooBar/bazCat", | ||||
| 			"foo", | ||||
| 			"FooBar.BazCat", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo/bar", | ||||
| 			"v1.foo", | ||||
| 			"Foo.Bar", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo/bar/baz", | ||||
| 			"v1.foo", | ||||
| 			"Bar.Baz", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo/bar/baz/cat", | ||||
| 			"v1.foo.bar", | ||||
| 			"Baz.Cat", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, d := range testData { | ||||
| 		s, m := apiRoute(d.path) | ||||
| 		if d.service != s { | ||||
| 			t.Fatalf("Expected service: %s for path: %s got: %s %s", d.service, d.path, s, m) | ||||
| 		} | ||||
| 		if d.method != m { | ||||
| 			t.Fatalf("Expected service: %s for path: %s got: %s", d.method, d.path, m) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestProxyRoute(t *testing.T) { | ||||
| 	testData := []struct { | ||||
| 		path    string | ||||
| 		service string | ||||
| 	}{ | ||||
| 		// no namespace | ||||
| 		{ | ||||
| 			"/f", | ||||
| 			"f", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/f", | ||||
| 			"f", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/f-b", | ||||
| 			"f-b", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/bar", | ||||
| 			"foo", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo-bar", | ||||
| 			"foo-bar", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo-bar-baz", | ||||
| 			"foo-bar-baz", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/bar/bar", | ||||
| 			"foo", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo/bar", | ||||
| 			"v1.foo", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo/bar/baz", | ||||
| 			"v1.foo", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo/bar/baz/cat", | ||||
| 			"v1.foo", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, d := range testData { | ||||
| 		s := proxyRoute(d.path) | ||||
| 		if d.service != s { | ||||
| 			t.Fatalf("Expected service: %s for path: %s got: %s", d.service, d.path, s) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										24
									
								
								api/resolver/options.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								api/resolver/options.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| package resolver | ||||
|  | ||||
| // NewOptions returns new initialised options | ||||
| func NewOptions(opts ...Option) Options { | ||||
| 	var options Options | ||||
| 	for _, o := range opts { | ||||
| 		o(&options) | ||||
| 	} | ||||
| 	return options | ||||
| } | ||||
|  | ||||
| // WithHandler sets the handler being used | ||||
| func WithHandler(h string) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.Handler = h | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithNamespace sets the namespace being used | ||||
| func WithNamespace(n string) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.Namespace = n | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										33
									
								
								api/resolver/path/path.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								api/resolver/path/path.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| // Package path resolves using http path | ||||
| package path | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/micro/go-micro/api/resolver" | ||||
| ) | ||||
|  | ||||
| type Resolver struct{} | ||||
|  | ||||
| func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { | ||||
| 	if req.URL.Path == "/" { | ||||
| 		return nil, errors.New("unknown name") | ||||
| 	} | ||||
| 	parts := strings.Split(req.URL.Path[1:], "/") | ||||
| 	return &resolver.Endpoint{ | ||||
| 		Name:   parts[0], | ||||
| 		Host:   req.Host, | ||||
| 		Method: req.Method, | ||||
| 		Path:   req.URL.Path, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (r *Resolver) String() string { | ||||
| 	return "path" | ||||
| } | ||||
|  | ||||
| func NewResolver(opts ...resolver.Option) resolver.Resolver { | ||||
| 	return &Resolver{} | ||||
| } | ||||
							
								
								
									
										31
									
								
								api/resolver/resolver.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								api/resolver/resolver.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| // Package resolver resolves a http request to an endpoint | ||||
| package resolver | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| // Resolver resolves requests to endpoints | ||||
| type Resolver interface { | ||||
| 	Resolve(r *http.Request) (*Endpoint, error) | ||||
| 	String() string | ||||
| } | ||||
|  | ||||
| // Endpoint is the endpoint for a http request | ||||
| type Endpoint struct { | ||||
| 	// e.g greeter | ||||
| 	Name string | ||||
| 	// HTTP Host e.g example.com | ||||
| 	Host string | ||||
| 	// HTTP Methods e.g GET, POST | ||||
| 	Method string | ||||
| 	// HTTP Path e.g /greeter. | ||||
| 	Path string | ||||
| } | ||||
|  | ||||
| type Options struct { | ||||
| 	Handler   string | ||||
| 	Namespace string | ||||
| } | ||||
|  | ||||
| type Option func(o *Options) | ||||
							
								
								
									
										59
									
								
								api/resolver/vpath/vpath.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								api/resolver/vpath/vpath.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| // Package vpath resolves using http path and recognised versioned urls | ||||
| package vpath | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/http" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/micro/go-micro/api/resolver" | ||||
| ) | ||||
|  | ||||
| type Resolver struct{} | ||||
|  | ||||
| var ( | ||||
| 	re = regexp.MustCompile("^v[0-9]+$") | ||||
| ) | ||||
|  | ||||
| func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { | ||||
| 	if req.URL.Path == "/" { | ||||
| 		return nil, errors.New("unknown name") | ||||
| 	} | ||||
|  | ||||
| 	parts := strings.Split(req.URL.Path[1:], "/") | ||||
|  | ||||
| 	if len(parts) == 1 { | ||||
| 		return &resolver.Endpoint{ | ||||
| 			Name:   parts[0], | ||||
| 			Host:   req.Host, | ||||
| 			Method: req.Method, | ||||
| 			Path:   req.URL.Path, | ||||
| 		}, nil | ||||
| 	} | ||||
|  | ||||
| 	// /v1/foo | ||||
| 	if re.MatchString(parts[0]) { | ||||
| 		return &resolver.Endpoint{ | ||||
| 			Name:   parts[1], | ||||
| 			Host:   req.Host, | ||||
| 			Method: req.Method, | ||||
| 			Path:   req.URL.Path, | ||||
| 		}, nil | ||||
| 	} | ||||
|  | ||||
| 	return &resolver.Endpoint{ | ||||
| 		Name:   parts[0], | ||||
| 		Host:   req.Host, | ||||
| 		Method: req.Method, | ||||
| 		Path:   req.URL.Path, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (r *Resolver) String() string { | ||||
| 	return "path" | ||||
| } | ||||
|  | ||||
| func NewResolver(opts ...resolver.Option) resolver.Resolver { | ||||
| 	return &Resolver{} | ||||
| } | ||||
							
								
								
									
										61
									
								
								api/router/options.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								api/router/options.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| package router | ||||
|  | ||||
| import ( | ||||
| 	"github.com/micro/go-micro/api/resolver" | ||||
| 	"github.com/micro/go-micro/api/resolver/micro" | ||||
| 	"github.com/micro/go-micro/cmd" | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| ) | ||||
|  | ||||
| type Options struct { | ||||
| 	Namespace string | ||||
| 	Handler   string | ||||
| 	Registry  registry.Registry | ||||
| 	Resolver  resolver.Resolver | ||||
| } | ||||
|  | ||||
| type Option func(o *Options) | ||||
|  | ||||
| func NewOptions(opts ...Option) Options { | ||||
| 	options := Options{ | ||||
| 		Handler:  "meta", | ||||
| 		Registry: *cmd.DefaultOptions().Registry, | ||||
| 	} | ||||
|  | ||||
| 	for _, o := range opts { | ||||
| 		o(&options) | ||||
| 	} | ||||
|  | ||||
| 	if options.Resolver == nil { | ||||
| 		options.Resolver = micro.NewResolver( | ||||
| 			resolver.WithHandler(options.Handler), | ||||
| 			resolver.WithNamespace(options.Namespace), | ||||
| 		) | ||||
| 	} | ||||
|  | ||||
| 	return options | ||||
| } | ||||
|  | ||||
| func WithHandler(h string) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.Handler = h | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func WithNamespace(ns string) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.Namespace = ns | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func WithRegistry(r registry.Registry) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.Registry = r | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func WithResolver(r resolver.Resolver) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.Resolver = r | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										393
									
								
								api/router/registry/registry.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										393
									
								
								api/router/registry/registry.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,393 @@ | ||||
| // Package registry provides a dynamic api service router | ||||
| package registry | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/micro/go-micro/api" | ||||
| 	"github.com/micro/go-micro/api/router" | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/registry/cache" | ||||
| ) | ||||
|  | ||||
| // router is the default router | ||||
| type registryRouter struct { | ||||
| 	exit chan bool | ||||
| 	opts router.Options | ||||
|  | ||||
| 	// registry cache | ||||
| 	rc cache.Cache | ||||
|  | ||||
| 	sync.RWMutex | ||||
| 	eps map[string]*api.Service | ||||
| } | ||||
|  | ||||
| func setNamespace(ns, name string) string { | ||||
| 	ns = strings.TrimSpace(ns) | ||||
| 	name = strings.TrimSpace(name) | ||||
|  | ||||
| 	// no namespace | ||||
| 	if len(ns) == 0 { | ||||
| 		return name | ||||
| 	} | ||||
|  | ||||
| 	switch { | ||||
| 	// has - suffix | ||||
| 	case strings.HasSuffix(ns, "-"): | ||||
| 		return strings.Replace(ns+name, ".", "-", -1) | ||||
| 	// has . suffix | ||||
| 	case strings.HasSuffix(ns, "."): | ||||
| 		return ns + name | ||||
| 	} | ||||
|  | ||||
| 	// default join . | ||||
| 	return strings.Join([]string{ns, name}, ".") | ||||
| } | ||||
|  | ||||
| func (r *registryRouter) isClosed() bool { | ||||
| 	select { | ||||
| 	case <-r.exit: | ||||
| 		return true | ||||
| 	default: | ||||
| 		return false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // refresh list of api services | ||||
| func (r *registryRouter) refresh() { | ||||
| 	var attempts int | ||||
|  | ||||
| 	for { | ||||
| 		services, err := r.opts.Registry.ListServices() | ||||
| 		if err != nil { | ||||
| 			attempts++ | ||||
| 			log.Println("Error listing endpoints", err) | ||||
| 			time.Sleep(time.Duration(attempts) * time.Second) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		attempts = 0 | ||||
|  | ||||
| 		// for each service, get service and store endpoints | ||||
| 		for _, s := range services { | ||||
| 			// only get services for this namespace | ||||
| 			if !strings.HasPrefix(s.Name, r.opts.Namespace) { | ||||
| 				continue | ||||
| 			} | ||||
| 			service, err := r.rc.GetService(s.Name) | ||||
| 			if err != nil { | ||||
| 				continue | ||||
| 			} | ||||
| 			r.store(service) | ||||
| 		} | ||||
|  | ||||
| 		// refresh list in 10 minutes... cruft | ||||
| 		select { | ||||
| 		case <-time.After(time.Minute * 10): | ||||
| 		case <-r.exit: | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // process watch event | ||||
| func (r *registryRouter) process(res *registry.Result) { | ||||
| 	// skip these things | ||||
| 	if res == nil || res.Service == nil || !strings.HasPrefix(res.Service.Name, r.opts.Namespace) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// get entry from cache | ||||
| 	service, err := r.rc.GetService(res.Service.Name) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// update our local endpoints | ||||
| 	r.store(service) | ||||
| } | ||||
|  | ||||
| // store local endpoint cache | ||||
| func (r *registryRouter) store(services []*registry.Service) { | ||||
| 	// endpoints | ||||
| 	eps := map[string]*api.Service{} | ||||
|  | ||||
| 	// services | ||||
| 	names := map[string]bool{} | ||||
|  | ||||
| 	// create a new endpoint mapping | ||||
| 	for _, service := range services { | ||||
| 		// set names we need later | ||||
| 		names[service.Name] = true | ||||
|  | ||||
| 		// map per endpoint | ||||
| 		for _, endpoint := range service.Endpoints { | ||||
| 			// create a key service:endpoint_name | ||||
| 			key := fmt.Sprintf("%s:%s", service.Name, endpoint.Name) | ||||
| 			// decode endpoint | ||||
| 			end := api.Decode(endpoint.Metadata) | ||||
|  | ||||
| 			// if we got nothing skip | ||||
| 			if err := api.Validate(end); err != nil { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			// try get endpoint | ||||
| 			ep, ok := eps[key] | ||||
| 			if !ok { | ||||
| 				ep = &api.Service{Name: service.Name} | ||||
| 			} | ||||
|  | ||||
| 			// overwrite the endpoint | ||||
| 			ep.Endpoint = end | ||||
| 			// append services | ||||
| 			ep.Services = append(ep.Services, service) | ||||
| 			// store it | ||||
| 			eps[key] = ep | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	r.Lock() | ||||
| 	defer r.Unlock() | ||||
|  | ||||
| 	// delete any existing eps for services we know | ||||
| 	for key, service := range r.eps { | ||||
| 		// skip what we don't care about | ||||
| 		if !names[service.Name] { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// ok we know this thing | ||||
| 		// delete delete delete | ||||
| 		delete(r.eps, key) | ||||
| 	} | ||||
|  | ||||
| 	// now set the eps we have | ||||
| 	for name, endpoint := range eps { | ||||
| 		r.eps[name] = endpoint | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // watch for endpoint changes | ||||
| func (r *registryRouter) watch() { | ||||
| 	var attempts int | ||||
|  | ||||
| 	for { | ||||
| 		if r.isClosed() { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// watch for changes | ||||
| 		w, err := r.opts.Registry.Watch() | ||||
| 		if err != nil { | ||||
| 			attempts++ | ||||
| 			log.Println("Error watching endpoints", err) | ||||
| 			time.Sleep(time.Duration(attempts) * time.Second) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		ch := make(chan bool) | ||||
|  | ||||
| 		go func() { | ||||
| 			select { | ||||
| 			case <-ch: | ||||
| 				w.Stop() | ||||
| 			case <-r.exit: | ||||
| 				w.Stop() | ||||
| 			} | ||||
| 		}() | ||||
|  | ||||
| 		// reset if we get here | ||||
| 		attempts = 0 | ||||
|  | ||||
| 		for { | ||||
| 			// process next event | ||||
| 			res, err := w.Next() | ||||
| 			if err != nil { | ||||
| 				log.Println("Error getting next endpoint", err) | ||||
| 				close(ch) | ||||
| 				break | ||||
| 			} | ||||
| 			r.process(res) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (r *registryRouter) Options() router.Options { | ||||
| 	return r.opts | ||||
| } | ||||
|  | ||||
| func (r *registryRouter) Close() error { | ||||
| 	select { | ||||
| 	case <-r.exit: | ||||
| 		return nil | ||||
| 	default: | ||||
| 		close(r.exit) | ||||
| 		r.rc.Stop() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (r *registryRouter) Endpoint(req *http.Request) (*api.Service, error) { | ||||
| 	if r.isClosed() { | ||||
| 		return nil, errors.New("router closed") | ||||
| 	} | ||||
|  | ||||
| 	r.RLock() | ||||
| 	defer r.RUnlock() | ||||
|  | ||||
| 	// use the first match | ||||
| 	// TODO: weighted matching | ||||
| 	for _, e := range r.eps { | ||||
| 		ep := e.Endpoint | ||||
|  | ||||
| 		// match | ||||
| 		var pathMatch, hostMatch, methodMatch bool | ||||
|  | ||||
| 		// 1. try method GET, POST, PUT, etc | ||||
| 		// 2. try host example.com, foobar.com, etc | ||||
| 		// 3. try path /foo/bar, /bar/baz, etc | ||||
|  | ||||
| 		// 1. try match method | ||||
| 		for _, m := range ep.Method { | ||||
| 			if req.Method == m { | ||||
| 				methodMatch = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// no match on method pass | ||||
| 		if len(ep.Method) > 0 && !methodMatch { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// 2. try match host | ||||
| 		for _, h := range ep.Host { | ||||
| 			if req.Host == h { | ||||
| 				hostMatch = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// no match on host pass | ||||
| 		if len(ep.Host) > 0 && !hostMatch { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// 3. try match paths | ||||
| 		for _, p := range ep.Path { | ||||
| 			re, err := regexp.CompilePOSIX(p) | ||||
| 			if err == nil && re.MatchString(req.URL.Path) { | ||||
| 				pathMatch = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// no match pass | ||||
| 		if len(ep.Path) > 0 && !pathMatch { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// TODO: Percentage traffic | ||||
|  | ||||
| 		// we got here, so its a match | ||||
| 		return e, nil | ||||
| 	} | ||||
|  | ||||
| 	// no match | ||||
| 	return nil, errors.New("not found") | ||||
| } | ||||
|  | ||||
| func (r *registryRouter) Route(req *http.Request) (*api.Service, error) { | ||||
| 	if r.isClosed() { | ||||
| 		return nil, errors.New("router closed") | ||||
| 	} | ||||
|  | ||||
| 	// try get an endpoint | ||||
| 	ep, err := r.Endpoint(req) | ||||
| 	if err == nil { | ||||
| 		return ep, nil | ||||
| 	} | ||||
|  | ||||
| 	// error not nil | ||||
| 	// ignore that shit | ||||
| 	// TODO: don't ignore that shit | ||||
|  | ||||
| 	// get the service name | ||||
| 	rp, err := r.opts.Resolver.Resolve(req) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// service name | ||||
| 	name := setNamespace(r.opts.Namespace, rp.Name) | ||||
|  | ||||
| 	// get service | ||||
| 	services, err := r.rc.GetService(name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// only use endpoint matching when the meta handler is set aka api.Default | ||||
| 	switch r.opts.Handler { | ||||
| 	// rpc handlers | ||||
| 	case "meta", "api", "rpc": | ||||
| 		handler := r.opts.Handler | ||||
|  | ||||
| 		// set default handler to api | ||||
| 		if r.opts.Handler == "meta" { | ||||
| 			handler = "rpc" | ||||
| 		} | ||||
|  | ||||
| 		// construct api service | ||||
| 		return &api.Service{ | ||||
| 			Name: name, | ||||
| 			Endpoint: &api.Endpoint{ | ||||
| 				Name:    rp.Method, | ||||
| 				Handler: handler, | ||||
| 			}, | ||||
| 			Services: services, | ||||
| 		}, nil | ||||
| 	// http handler | ||||
| 	case "http", "proxy", "web": | ||||
| 		// construct api service | ||||
| 		return &api.Service{ | ||||
| 			Name: name, | ||||
| 			Endpoint: &api.Endpoint{ | ||||
| 				Name:    req.URL.String(), | ||||
| 				Handler: r.opts.Handler, | ||||
| 				Host:    []string{req.Host}, | ||||
| 				Method:  []string{req.Method}, | ||||
| 				Path:    []string{req.URL.Path}, | ||||
| 			}, | ||||
| 			Services: services, | ||||
| 		}, nil | ||||
| 	} | ||||
|  | ||||
| 	return nil, errors.New("unknown handler") | ||||
| } | ||||
|  | ||||
| func newRouter(opts ...router.Option) *registryRouter { | ||||
| 	options := router.NewOptions(opts...) | ||||
| 	r := ®istryRouter{ | ||||
| 		exit: make(chan bool), | ||||
| 		opts: options, | ||||
| 		rc:   cache.New(options.Registry), | ||||
| 		eps:  make(map[string]*api.Service), | ||||
| 	} | ||||
| 	go r.watch() | ||||
| 	go r.refresh() | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // NewRouter returns the default router | ||||
| func NewRouter(opts ...router.Option) router.Router { | ||||
| 	return newRouter(opts...) | ||||
| } | ||||
							
								
								
									
										181
									
								
								api/router/registry/registry_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								api/router/registry/registry_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | ||||
| package registry | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/micro/go-micro/api" | ||||
| ) | ||||
|  | ||||
| func TestSetNamespace(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		namespace string | ||||
| 		name      string | ||||
| 		expected  string | ||||
| 	}{ | ||||
| 		// default dotted path | ||||
| 		{ | ||||
| 			"go.micro.api", | ||||
| 			"foo", | ||||
| 			"go.micro.api.foo", | ||||
| 		}, | ||||
| 		// dotted end | ||||
| 		{ | ||||
| 			"go.micro.api.", | ||||
| 			"foo", | ||||
| 			"go.micro.api.foo", | ||||
| 		}, | ||||
| 		// dashed end | ||||
| 		{ | ||||
| 			"go-micro-api-", | ||||
| 			"foo", | ||||
| 			"go-micro-api-foo", | ||||
| 		}, | ||||
| 		// no namespace | ||||
| 		{ | ||||
| 			"", | ||||
| 			"foo", | ||||
| 			"foo", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"go-micro-api-", | ||||
| 			"v2.foo", | ||||
| 			"go-micro-api-v2-foo", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, test := range testCases { | ||||
| 		name := setNamespace(test.namespace, test.name) | ||||
| 		if name != test.expected { | ||||
| 			t.Fatalf("expected name %s got %s", test.expected, name) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestRouter(t *testing.T) { | ||||
| 	r := newRouter() | ||||
|  | ||||
| 	compare := func(expect, got []string) bool { | ||||
| 		// no data to compare, return true | ||||
| 		if len(expect) == 0 && len(got) == 0 { | ||||
| 			return true | ||||
| 		} | ||||
| 		// no data expected but got some return false | ||||
| 		if len(expect) == 0 && len(got) > 0 { | ||||
| 			return false | ||||
| 		} | ||||
|  | ||||
| 		// compare expected with what we got | ||||
| 		for _, e := range expect { | ||||
| 			var seen bool | ||||
| 			for _, g := range got { | ||||
| 				if e == g { | ||||
| 					seen = true | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 			if !seen { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// we're done, return true | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	testData := []struct { | ||||
| 		e *api.Endpoint | ||||
| 		r *http.Request | ||||
| 		m bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			e: &api.Endpoint{ | ||||
| 				Name:   "Foo.Bar", | ||||
| 				Host:   []string{"example.com"}, | ||||
| 				Method: []string{"GET"}, | ||||
| 				Path:   []string{"/foo"}, | ||||
| 			}, | ||||
| 			r: &http.Request{ | ||||
| 				Host:   "example.com", | ||||
| 				Method: "GET", | ||||
| 				URL: &url.URL{ | ||||
| 					Path: "/foo", | ||||
| 				}, | ||||
| 			}, | ||||
| 			m: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			e: &api.Endpoint{ | ||||
| 				Name:   "Bar.Baz", | ||||
| 				Host:   []string{"example.com", "foo.com"}, | ||||
| 				Method: []string{"GET", "POST"}, | ||||
| 				Path:   []string{"/foo/bar"}, | ||||
| 			}, | ||||
| 			r: &http.Request{ | ||||
| 				Host:   "foo.com", | ||||
| 				Method: "POST", | ||||
| 				URL: &url.URL{ | ||||
| 					Path: "/foo/bar", | ||||
| 				}, | ||||
| 			}, | ||||
| 			m: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			e: &api.Endpoint{ | ||||
| 				Name:   "Test.Cruft", | ||||
| 				Host:   []string{"example.com", "foo.com"}, | ||||
| 				Method: []string{"GET", "POST"}, | ||||
| 				Path:   []string{"/xyz"}, | ||||
| 			}, | ||||
| 			r: &http.Request{ | ||||
| 				Host:   "fail.com", | ||||
| 				Method: "DELETE", | ||||
| 				URL: &url.URL{ | ||||
| 					Path: "/test/fail", | ||||
| 				}, | ||||
| 			}, | ||||
| 			m: false, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, d := range testData { | ||||
| 		key := fmt.Sprintf("%s:%s", "test.service", d.e.Name) | ||||
| 		r.eps[key] = &api.Service{ | ||||
| 			Endpoint: d.e, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for _, d := range testData { | ||||
| 		e, err := r.Endpoint(d.r) | ||||
| 		if d.m && err != nil { | ||||
| 			t.Fatalf("expected match, got %v", err) | ||||
| 		} | ||||
| 		if !d.m && err == nil { | ||||
| 			t.Fatal("expected error got match") | ||||
| 		} | ||||
| 		// skip testing the non match | ||||
| 		if !d.m { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		ep := e.Endpoint | ||||
|  | ||||
| 		// test the match | ||||
| 		if d.e.Name != ep.Name { | ||||
| 			t.Fatalf("expected %v got %v", d.e.Name, ep.Name) | ||||
| 		} | ||||
| 		if ok := compare(d.e.Method, ep.Method); !ok { | ||||
| 			t.Fatalf("expected %v got %v", d.e.Method, ep.Method) | ||||
| 		} | ||||
| 		if ok := compare(d.e.Path, ep.Path); !ok { | ||||
| 			t.Fatalf("expected %v got %v", d.e.Path, ep.Path) | ||||
| 		} | ||||
| 		if ok := compare(d.e.Host, ep.Host); !ok { | ||||
| 			t.Fatalf("expected %v got %v", d.e.Host, ep.Host) | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
|  | ||||
| } | ||||
							
								
								
									
										20
									
								
								api/router/router.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								api/router/router.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| // Package router provides api service routing | ||||
| package router | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/micro/go-micro/api" | ||||
| ) | ||||
|  | ||||
| // Router is used to determine an endpoint for a request | ||||
| type Router interface { | ||||
| 	// Returns options | ||||
| 	Options() Options | ||||
| 	// Stop the router | ||||
| 	Close() error | ||||
| 	// Endpoint returns an api.Service endpoint or an error if it does not exist | ||||
| 	Endpoint(r *http.Request) (*api.Service, error) | ||||
| 	// Route returns an api.Service route | ||||
| 	Route(r *http.Request) (*api.Service, error) | ||||
| } | ||||
							
								
								
									
										98
									
								
								api/server/http/http.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								api/server/http/http.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | ||||
| // Package http provides a http server with features; acme, cors, etc | ||||
| package http | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/gorilla/handlers" | ||||
| 	"github.com/micro/go-micro/api/server" | ||||
| 	"github.com/micro/go-micro/util/log" | ||||
| 	"golang.org/x/crypto/acme/autocert" | ||||
| ) | ||||
|  | ||||
| type httpServer struct { | ||||
| 	mux  *http.ServeMux | ||||
| 	opts server.Options | ||||
|  | ||||
| 	mtx     sync.RWMutex | ||||
| 	address string | ||||
| 	exit    chan chan error | ||||
| } | ||||
|  | ||||
| func NewServer(address string) server.Server { | ||||
| 	return &httpServer{ | ||||
| 		opts:    server.Options{}, | ||||
| 		mux:     http.NewServeMux(), | ||||
| 		address: address, | ||||
| 		exit:    make(chan chan error), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *httpServer) Address() string { | ||||
| 	s.mtx.RLock() | ||||
| 	defer s.mtx.RUnlock() | ||||
| 	return s.address | ||||
| } | ||||
|  | ||||
| func (s *httpServer) Init(opts ...server.Option) error { | ||||
| 	for _, o := range opts { | ||||
| 		o(&s.opts) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (s *httpServer) Handle(path string, handler http.Handler) { | ||||
| 	s.mux.Handle(path, handlers.CombinedLoggingHandler(os.Stdout, handler)) | ||||
| } | ||||
|  | ||||
| func (s *httpServer) Start() error { | ||||
| 	var l net.Listener | ||||
| 	var err error | ||||
|  | ||||
| 	if s.opts.EnableACME { | ||||
| 		// should we check the address to make sure its using :443? | ||||
| 		l = autocert.NewListener(s.opts.ACMEHosts...) | ||||
| 	} else if s.opts.EnableTLS && s.opts.TLSConfig != nil { | ||||
| 		l, err = tls.Listen("tcp", s.address, s.opts.TLSConfig) | ||||
| 	} else { | ||||
| 		// otherwise plain listen | ||||
| 		l, err = net.Listen("tcp", s.address) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	log.Logf("HTTP API Listening on %s", l.Addr().String()) | ||||
|  | ||||
| 	s.mtx.Lock() | ||||
| 	s.address = l.Addr().String() | ||||
| 	s.mtx.Unlock() | ||||
|  | ||||
| 	go func() { | ||||
| 		if err := http.Serve(l, s.mux); err != nil { | ||||
| 			// temporary fix | ||||
| 			//log.Fatal(err) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	go func() { | ||||
| 		ch := <-s.exit | ||||
| 		ch <- l.Close() | ||||
| 	}() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (s *httpServer) Stop() error { | ||||
| 	ch := make(chan error) | ||||
| 	s.exit <- ch | ||||
| 	return <-ch | ||||
| } | ||||
|  | ||||
| func (s *httpServer) String() string { | ||||
| 	return "http" | ||||
| } | ||||
							
								
								
									
										41
									
								
								api/server/http/http_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								api/server/http/http_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| package http | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestHTTPServer(t *testing.T) { | ||||
| 	testResponse := "hello world" | ||||
|  | ||||
| 	s := NewServer("localhost:0") | ||||
|  | ||||
| 	s.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
| 		fmt.Fprint(w, testResponse) | ||||
| 	})) | ||||
|  | ||||
| 	if err := s.Start(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	rsp, err := http.Get(fmt.Sprintf("http://%s/", s.Address())) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer rsp.Body.Close() | ||||
|  | ||||
| 	b, err := ioutil.ReadAll(rsp.Body) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if string(b) != testResponse { | ||||
| 		t.Fatalf("Unexpected response, got %s, expected %s", string(b), testResponse) | ||||
| 	} | ||||
|  | ||||
| 	if err := s.Stop(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										38
									
								
								api/server/options.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								api/server/options.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| package server | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| ) | ||||
|  | ||||
| type Option func(o *Options) | ||||
|  | ||||
| type Options struct { | ||||
| 	EnableACME bool | ||||
| 	EnableTLS  bool | ||||
| 	ACMEHosts  []string | ||||
| 	TLSConfig  *tls.Config | ||||
| } | ||||
|  | ||||
| func ACMEHosts(hosts ...string) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.ACMEHosts = hosts | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func EnableACME(b bool) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.EnableACME = b | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func EnableTLS(b bool) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.EnableTLS = b | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TLSConfig(t *tls.Config) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.TLSConfig = t | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										15
									
								
								api/server/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								api/server/server.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| // Package server provides an API gateway server which handles inbound requests | ||||
| package server | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| // Server serves api requests | ||||
| type Server interface { | ||||
| 	Address() string | ||||
| 	Init(opts ...Option) error | ||||
| 	Handle(path string, handler http.Handler) | ||||
| 	Start() error | ||||
| 	Stop() error | ||||
| } | ||||
							
								
								
									
										25
									
								
								client/grpc/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								client/grpc/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| # GRPC Client | ||||
|  | ||||
| The grpc client is a [micro.Client](https://godoc.org/github.com/micro/go-micro/client#Client) compatible client. | ||||
|  | ||||
| ## Overview | ||||
|  | ||||
| The client makes use of the [google.golang.org/grpc](google.golang.org/grpc) framework for the underlying communication mechanism. | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
| Specify the client to your micro service | ||||
|  | ||||
| ```go | ||||
| import ( | ||||
| 	"github.com/micro/go-micro" | ||||
| 	"github.com/micro/go-plugins/client/grpc" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	service := micro.NewService( | ||||
| 		micro.Name("greeter"), | ||||
| 		micro.Client(grpc.NewClient()), | ||||
| 	) | ||||
| } | ||||
| ``` | ||||
							
								
								
									
										14
									
								
								client/grpc/buffer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								client/grpc/buffer.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| ) | ||||
|  | ||||
| type buffer struct { | ||||
| 	*bytes.Buffer | ||||
| } | ||||
|  | ||||
| func (b *buffer) Close() error { | ||||
| 	b.Buffer.Reset() | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										98
									
								
								client/grpc/codec.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								client/grpc/codec.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/golang/protobuf/proto" | ||||
| 	"github.com/json-iterator/go" | ||||
| 	"github.com/micro/go-micro/codec" | ||||
| 	"github.com/micro/go-micro/codec/jsonrpc" | ||||
| 	"github.com/micro/go-micro/codec/protorpc" | ||||
| 	"google.golang.org/grpc/encoding" | ||||
| ) | ||||
|  | ||||
| type jsonCodec struct{} | ||||
| type protoCodec struct{} | ||||
| type bytesCodec struct{} | ||||
| type wrapCodec struct{ encoding.Codec } | ||||
|  | ||||
| var ( | ||||
| 	defaultGRPCCodecs = map[string]encoding.Codec{ | ||||
| 		"application/json":         jsonCodec{}, | ||||
| 		"application/proto":        protoCodec{}, | ||||
| 		"application/protobuf":     protoCodec{}, | ||||
| 		"application/octet-stream": protoCodec{}, | ||||
| 		"application/grpc+json":    jsonCodec{}, | ||||
| 		"application/grpc+proto":   protoCodec{}, | ||||
| 		"application/grpc+bytes":   bytesCodec{}, | ||||
| 	} | ||||
|  | ||||
| 	defaultRPCCodecs = map[string]codec.NewCodec{ | ||||
| 		"application/json":         jsonrpc.NewCodec, | ||||
| 		"application/json-rpc":     jsonrpc.NewCodec, | ||||
| 		"application/protobuf":     protorpc.NewCodec, | ||||
| 		"application/proto-rpc":    protorpc.NewCodec, | ||||
| 		"application/octet-stream": protorpc.NewCodec, | ||||
| 	} | ||||
|  | ||||
| 	json = jsoniter.ConfigCompatibleWithStandardLibrary | ||||
| ) | ||||
|  | ||||
| // UseNumber fix unmarshal Number(8234567890123456789) to interface(8.234567890123457e+18) | ||||
| func UseNumber() { | ||||
| 	json = jsoniter.Config{ | ||||
| 		UseNumber:              true, | ||||
| 		EscapeHTML:             true, | ||||
| 		SortMapKeys:            true, | ||||
| 		ValidateJsonRawMessage: true, | ||||
| 	}.Froze() | ||||
| } | ||||
|  | ||||
| func (w wrapCodec) String() string { | ||||
| 	return w.Codec.Name() | ||||
| } | ||||
|  | ||||
| func (protoCodec) Marshal(v interface{}) ([]byte, error) { | ||||
| 	return proto.Marshal(v.(proto.Message)) | ||||
| } | ||||
|  | ||||
| func (protoCodec) Unmarshal(data []byte, v interface{}) error { | ||||
| 	return proto.Unmarshal(data, v.(proto.Message)) | ||||
| } | ||||
|  | ||||
| func (protoCodec) Name() string { | ||||
| 	return "proto" | ||||
| } | ||||
|  | ||||
| func (bytesCodec) Marshal(v interface{}) ([]byte, error) { | ||||
| 	b, ok := v.(*[]byte) | ||||
| 	if !ok { | ||||
| 		return nil, fmt.Errorf("failed to marshal: %v is not type of *[]byte", v) | ||||
| 	} | ||||
| 	return *b, nil | ||||
| } | ||||
|  | ||||
| func (bytesCodec) Unmarshal(data []byte, v interface{}) error { | ||||
| 	b, ok := v.(*[]byte) | ||||
| 	if !ok { | ||||
| 		return fmt.Errorf("failed to unmarshal: %v is not type of *[]byte", v) | ||||
| 	} | ||||
| 	*b = data | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (bytesCodec) Name() string { | ||||
| 	return "bytes" | ||||
| } | ||||
|  | ||||
| func (jsonCodec) Marshal(v interface{}) ([]byte, error) { | ||||
| 	return json.Marshal(v) | ||||
| } | ||||
|  | ||||
| func (jsonCodec) Unmarshal(data []byte, v interface{}) error { | ||||
| 	return json.Unmarshal(data, v) | ||||
| } | ||||
|  | ||||
| func (jsonCodec) Name() string { | ||||
| 	return "json" | ||||
| } | ||||
							
								
								
									
										30
									
								
								client/grpc/error.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								client/grpc/error.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"github.com/micro/go-micro/errors" | ||||
| 	"google.golang.org/grpc/status" | ||||
| ) | ||||
|  | ||||
| func microError(err error) error { | ||||
| 	// no error | ||||
| 	switch err { | ||||
| 	case nil: | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// micro error | ||||
| 	if v, ok := err.(*errors.Error); ok { | ||||
| 		return v | ||||
| 	} | ||||
|  | ||||
| 	// grpc error | ||||
| 	if s, ok := status.FromError(err); ok { | ||||
| 		if e := errors.Parse(s.Message()); e.Code > 0 { | ||||
| 			return e // actually a micro error | ||||
| 		} | ||||
| 		return errors.InternalServerError("go.micro.client", s.Message()) | ||||
| 	} | ||||
|  | ||||
| 	// do nothing | ||||
| 	return err | ||||
| } | ||||
							
								
								
									
										541
									
								
								client/grpc/grpc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										541
									
								
								client/grpc/grpc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,541 @@ | ||||
| // Package grpc provides a gRPC client | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"fmt" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/micro/go-micro/broker" | ||||
| 	"github.com/micro/go-micro/client" | ||||
| 	"github.com/micro/go-micro/cmd" | ||||
| 	"github.com/micro/go-micro/codec" | ||||
| 	"github.com/micro/go-micro/errors" | ||||
| 	"github.com/micro/go-micro/metadata" | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/selector" | ||||
| 	"github.com/micro/go-micro/transport" | ||||
|  | ||||
| 	"google.golang.org/grpc" | ||||
| 	"google.golang.org/grpc/credentials" | ||||
| 	"google.golang.org/grpc/encoding" | ||||
| 	gmetadata "google.golang.org/grpc/metadata" | ||||
| ) | ||||
|  | ||||
| type grpcClient struct { | ||||
| 	once sync.Once | ||||
| 	opts client.Options | ||||
| 	pool *pool | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	encoding.RegisterCodec(jsonCodec{}) | ||||
| 	encoding.RegisterCodec(bytesCodec{}) | ||||
|  | ||||
| 	cmd.DefaultClients["grpc"] = NewClient | ||||
| } | ||||
|  | ||||
| // secure returns the dial option for whether its a secure or insecure connection | ||||
| func (g *grpcClient) secure() grpc.DialOption { | ||||
| 	if g.opts.Context != nil { | ||||
| 		if v := g.opts.Context.Value(tlsAuth{}); v != nil { | ||||
| 			tls := v.(*tls.Config) | ||||
| 			creds := credentials.NewTLS(tls) | ||||
| 			return grpc.WithTransportCredentials(creds) | ||||
| 		} | ||||
| 	} | ||||
| 	return grpc.WithInsecure() | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) next(request client.Request, opts client.CallOptions) (selector.Next, error) { | ||||
| 	// return remote address | ||||
| 	if len(opts.Address) > 0 { | ||||
| 		return func() (*registry.Node, error) { | ||||
| 			return ®istry.Node{ | ||||
| 				Address: opts.Address, | ||||
| 			}, nil | ||||
| 		}, nil | ||||
| 	} | ||||
|  | ||||
| 	// get next nodes from the selector | ||||
| 	next, err := g.opts.Selector.Select(request.Service(), opts.SelectOptions...) | ||||
| 	if err != nil && err == selector.ErrNotFound { | ||||
| 		return nil, errors.NotFound("go.micro.client", err.Error()) | ||||
| 	} else if err != nil { | ||||
| 		return nil, errors.InternalServerError("go.micro.client", err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return next, nil | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error { | ||||
| 	address := node.Address | ||||
| 	if node.Port > 0 { | ||||
| 		address = fmt.Sprintf("%s:%d", address, node.Port) | ||||
| 	} | ||||
|  | ||||
| 	header := make(map[string]string) | ||||
| 	if md, ok := metadata.FromContext(ctx); ok { | ||||
| 		for k, v := range md { | ||||
| 			header[k] = v | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// set timeout in nanoseconds | ||||
| 	header["timeout"] = fmt.Sprintf("%d", opts.RequestTimeout) | ||||
| 	// set the content type for the request | ||||
| 	header["x-content-type"] = req.ContentType() | ||||
|  | ||||
| 	md := gmetadata.New(header) | ||||
| 	ctx = gmetadata.NewOutgoingContext(ctx, md) | ||||
|  | ||||
| 	cf, err := g.newGRPCCodec(req.ContentType()) | ||||
| 	if err != nil { | ||||
| 		return errors.InternalServerError("go.micro.client", err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	maxRecvMsgSize := g.maxRecvMsgSizeValue() | ||||
| 	maxSendMsgSize := g.maxSendMsgSizeValue() | ||||
|  | ||||
| 	var grr error | ||||
|  | ||||
| 	cc, err := g.pool.getConn(address, grpc.WithDefaultCallOptions(grpc.CallCustomCodec(cf)), | ||||
| 		grpc.WithTimeout(opts.DialTimeout), g.secure(), | ||||
| 		grpc.WithDefaultCallOptions( | ||||
| 			grpc.MaxCallRecvMsgSize(maxRecvMsgSize), | ||||
| 			grpc.MaxCallSendMsgSize(maxSendMsgSize), | ||||
| 		)) | ||||
| 	if err != nil { | ||||
| 		return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err)) | ||||
| 	} | ||||
| 	defer func() { | ||||
| 		// defer execution of release | ||||
| 		g.pool.release(address, cc, grr) | ||||
| 	}() | ||||
|  | ||||
| 	ch := make(chan error, 1) | ||||
|  | ||||
| 	go func() { | ||||
| 		err := cc.Invoke(ctx, methodToGRPC(req.Endpoint(), req.Body()), req.Body(), rsp, grpc.CallContentSubtype(cf.String())) | ||||
| 		ch <- microError(err) | ||||
| 	}() | ||||
|  | ||||
| 	select { | ||||
| 	case err := <-ch: | ||||
| 		grr = err | ||||
| 	case <-ctx.Done(): | ||||
| 		grr = ctx.Err() | ||||
| 	} | ||||
|  | ||||
| 	return grr | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) stream(ctx context.Context, node *registry.Node, req client.Request, opts client.CallOptions) (client.Stream, error) { | ||||
| 	address := node.Address | ||||
| 	if node.Port > 0 { | ||||
| 		address = fmt.Sprintf("%s:%d", address, node.Port) | ||||
| 	} | ||||
|  | ||||
| 	header := make(map[string]string) | ||||
| 	if md, ok := metadata.FromContext(ctx); ok { | ||||
| 		for k, v := range md { | ||||
| 			header[k] = v | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// set timeout in nanoseconds | ||||
| 	header["timeout"] = fmt.Sprintf("%d", opts.RequestTimeout) | ||||
| 	// set the content type for the request | ||||
| 	header["x-content-type"] = req.ContentType() | ||||
|  | ||||
| 	md := gmetadata.New(header) | ||||
| 	ctx = gmetadata.NewOutgoingContext(ctx, md) | ||||
|  | ||||
| 	cf, err := g.newGRPCCodec(req.ContentType()) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.InternalServerError("go.micro.client", err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	var dialCtx context.Context | ||||
| 	var cancel context.CancelFunc | ||||
| 	if opts.DialTimeout >= 0 { | ||||
| 		dialCtx, cancel = context.WithTimeout(ctx, opts.DialTimeout) | ||||
| 	} else { | ||||
| 		dialCtx, cancel = context.WithCancel(ctx) | ||||
| 	} | ||||
| 	defer cancel() | ||||
| 	cc, err := grpc.DialContext(dialCtx, address, grpc.WithDefaultCallOptions(grpc.CallCustomCodec(cf)), g.secure()) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err)) | ||||
| 	} | ||||
|  | ||||
| 	desc := &grpc.StreamDesc{ | ||||
| 		StreamName:    req.Service() + req.Endpoint(), | ||||
| 		ClientStreams: true, | ||||
| 		ServerStreams: true, | ||||
| 	} | ||||
|  | ||||
| 	st, err := cc.NewStream(ctx, desc, methodToGRPC(req.Endpoint(), req.Body()), grpc.CallContentSubtype(cf.String())) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error creating stream: %v", err)) | ||||
| 	} | ||||
|  | ||||
| 	return &grpcStream{ | ||||
| 		context: ctx, | ||||
| 		request: req, | ||||
| 		stream:  st, | ||||
| 		conn:    cc, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) maxRecvMsgSizeValue() int { | ||||
| 	if g.opts.Context == nil { | ||||
| 		return DefaultMaxRecvMsgSize | ||||
| 	} | ||||
| 	v := g.opts.Context.Value(maxRecvMsgSizeKey{}) | ||||
| 	if v == nil { | ||||
| 		return DefaultMaxRecvMsgSize | ||||
| 	} | ||||
| 	return v.(int) | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) maxSendMsgSizeValue() int { | ||||
| 	if g.opts.Context == nil { | ||||
| 		return DefaultMaxSendMsgSize | ||||
| 	} | ||||
| 	v := g.opts.Context.Value(maxSendMsgSizeKey{}) | ||||
| 	if v == nil { | ||||
| 		return DefaultMaxSendMsgSize | ||||
| 	} | ||||
| 	return v.(int) | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) newGRPCCodec(contentType string) (grpc.Codec, error) { | ||||
| 	codecs := make(map[string]encoding.Codec) | ||||
| 	if g.opts.Context != nil { | ||||
| 		if v := g.opts.Context.Value(codecsKey{}); v != nil { | ||||
| 			codecs = v.(map[string]encoding.Codec) | ||||
| 		} | ||||
| 	} | ||||
| 	if c, ok := codecs[contentType]; ok { | ||||
| 		return wrapCodec{c}, nil | ||||
| 	} | ||||
| 	if c, ok := defaultGRPCCodecs[contentType]; ok { | ||||
| 		return wrapCodec{c}, nil | ||||
| 	} | ||||
| 	return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType) | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) newCodec(contentType string) (codec.NewCodec, error) { | ||||
| 	if c, ok := g.opts.Codecs[contentType]; ok { | ||||
| 		return c, nil | ||||
| 	} | ||||
| 	if cf, ok := defaultRPCCodecs[contentType]; ok { | ||||
| 		return cf, nil | ||||
| 	} | ||||
| 	return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType) | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) Init(opts ...client.Option) error { | ||||
| 	size := g.opts.PoolSize | ||||
| 	ttl := g.opts.PoolTTL | ||||
|  | ||||
| 	for _, o := range opts { | ||||
| 		o(&g.opts) | ||||
| 	} | ||||
|  | ||||
| 	// update pool configuration if the options changed | ||||
| 	if size != g.opts.PoolSize || ttl != g.opts.PoolTTL { | ||||
| 		g.pool.Lock() | ||||
| 		g.pool.size = g.opts.PoolSize | ||||
| 		g.pool.ttl = int64(g.opts.PoolTTL.Seconds()) | ||||
| 		g.pool.Unlock() | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) Options() client.Options { | ||||
| 	return g.opts | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) NewMessage(topic string, msg interface{}, opts ...client.MessageOption) client.Message { | ||||
| 	return newGRPCPublication(topic, msg, "application/octet-stream") | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) NewRequest(service, method string, req interface{}, reqOpts ...client.RequestOption) client.Request { | ||||
| 	return newGRPCRequest(service, method, req, g.opts.ContentType, reqOpts...) | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error { | ||||
| 	// make a copy of call opts | ||||
| 	callOpts := g.opts.CallOptions | ||||
| 	for _, opt := range opts { | ||||
| 		opt(&callOpts) | ||||
| 	} | ||||
|  | ||||
| 	next, err := g.next(req, callOpts) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// check if we already have a deadline | ||||
| 	d, ok := ctx.Deadline() | ||||
| 	if !ok { | ||||
| 		// no deadline so we create a new one | ||||
| 		ctx, _ = context.WithTimeout(ctx, callOpts.RequestTimeout) | ||||
| 	} else { | ||||
| 		// got a deadline so no need to setup context | ||||
| 		// but we need to set the timeout we pass along | ||||
| 		opt := client.WithRequestTimeout(time.Until(d)) | ||||
| 		opt(&callOpts) | ||||
| 	} | ||||
|  | ||||
| 	// should we noop right here? | ||||
| 	select { | ||||
| 	case <-ctx.Done(): | ||||
| 		return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) | ||||
| 	default: | ||||
| 	} | ||||
|  | ||||
| 	// make copy of call method | ||||
| 	gcall := g.call | ||||
|  | ||||
| 	// wrap the call in reverse | ||||
| 	for i := len(callOpts.CallWrappers); i > 0; i-- { | ||||
| 		gcall = callOpts.CallWrappers[i-1](gcall) | ||||
| 	} | ||||
|  | ||||
| 	// return errors.New("go.micro.client", "request timeout", 408) | ||||
| 	call := func(i int) error { | ||||
| 		// call backoff first. Someone may want an initial start delay | ||||
| 		t, err := callOpts.Backoff(ctx, req, i) | ||||
| 		if err != nil { | ||||
| 			return errors.InternalServerError("go.micro.client", err.Error()) | ||||
| 		} | ||||
|  | ||||
| 		// only sleep if greater than 0 | ||||
| 		if t.Seconds() > 0 { | ||||
| 			time.Sleep(t) | ||||
| 		} | ||||
|  | ||||
| 		// select next node | ||||
| 		node, err := next() | ||||
| 		if err != nil && err == selector.ErrNotFound { | ||||
| 			return errors.NotFound("go.micro.client", err.Error()) | ||||
| 		} else if err != nil { | ||||
| 			return errors.InternalServerError("go.micro.client", err.Error()) | ||||
| 		} | ||||
|  | ||||
| 		// make the call | ||||
| 		err = gcall(ctx, node, req, rsp, callOpts) | ||||
| 		g.opts.Selector.Mark(req.Service(), node, err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	ch := make(chan error, callOpts.Retries+1) | ||||
| 	var gerr error | ||||
|  | ||||
| 	for i := 0; i <= callOpts.Retries; i++ { | ||||
| 		go func() { | ||||
| 			ch <- call(i) | ||||
| 		}() | ||||
|  | ||||
| 		select { | ||||
| 		case <-ctx.Done(): | ||||
| 			return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) | ||||
| 		case err := <-ch: | ||||
| 			// if the call succeeded lets bail early | ||||
| 			if err == nil { | ||||
| 				return nil | ||||
| 			} | ||||
|  | ||||
| 			retry, rerr := callOpts.Retry(ctx, req, i, err) | ||||
| 			if rerr != nil { | ||||
| 				return rerr | ||||
| 			} | ||||
|  | ||||
| 			if !retry { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			gerr = err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return gerr | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) { | ||||
| 	// make a copy of call opts | ||||
| 	callOpts := g.opts.CallOptions | ||||
| 	for _, opt := range opts { | ||||
| 		opt(&callOpts) | ||||
| 	} | ||||
|  | ||||
| 	next, err := g.next(req, callOpts) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// #200 - streams shouldn't have a request timeout set on the context | ||||
|  | ||||
| 	// should we noop right here? | ||||
| 	select { | ||||
| 	case <-ctx.Done(): | ||||
| 		return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) | ||||
| 	default: | ||||
| 	} | ||||
|  | ||||
| 	call := func(i int) (client.Stream, error) { | ||||
| 		// call backoff first. Someone may want an initial start delay | ||||
| 		t, err := callOpts.Backoff(ctx, req, i) | ||||
| 		if err != nil { | ||||
| 			return nil, errors.InternalServerError("go.micro.client", err.Error()) | ||||
| 		} | ||||
|  | ||||
| 		// only sleep if greater than 0 | ||||
| 		if t.Seconds() > 0 { | ||||
| 			time.Sleep(t) | ||||
| 		} | ||||
|  | ||||
| 		node, err := next() | ||||
| 		if err != nil && err == selector.ErrNotFound { | ||||
| 			return nil, errors.NotFound("go.micro.client", err.Error()) | ||||
| 		} else if err != nil { | ||||
| 			return nil, errors.InternalServerError("go.micro.client", err.Error()) | ||||
| 		} | ||||
|  | ||||
| 		stream, err := g.stream(ctx, node, req, callOpts) | ||||
| 		g.opts.Selector.Mark(req.Service(), node, err) | ||||
| 		return stream, err | ||||
| 	} | ||||
|  | ||||
| 	type response struct { | ||||
| 		stream client.Stream | ||||
| 		err    error | ||||
| 	} | ||||
|  | ||||
| 	ch := make(chan response, callOpts.Retries+1) | ||||
| 	var grr error | ||||
|  | ||||
| 	for i := 0; i <= callOpts.Retries; i++ { | ||||
| 		go func() { | ||||
| 			s, err := call(i) | ||||
| 			ch <- response{s, err} | ||||
| 		}() | ||||
|  | ||||
| 		select { | ||||
| 		case <-ctx.Done(): | ||||
| 			return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) | ||||
| 		case rsp := <-ch: | ||||
| 			// if the call succeeded lets bail early | ||||
| 			if rsp.err == nil { | ||||
| 				return rsp.stream, nil | ||||
| 			} | ||||
|  | ||||
| 			retry, rerr := callOpts.Retry(ctx, req, i, err) | ||||
| 			if rerr != nil { | ||||
| 				return nil, rerr | ||||
| 			} | ||||
|  | ||||
| 			if !retry { | ||||
| 				return nil, rsp.err | ||||
| 			} | ||||
|  | ||||
| 			grr = rsp.err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil, grr | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error { | ||||
| 	md, ok := metadata.FromContext(ctx) | ||||
| 	if !ok { | ||||
| 		md = make(map[string]string) | ||||
| 	} | ||||
| 	md["Content-Type"] = p.ContentType() | ||||
|  | ||||
| 	cf, err := g.newCodec(p.ContentType()) | ||||
| 	if err != nil { | ||||
| 		return errors.InternalServerError("go.micro.client", err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	b := &buffer{bytes.NewBuffer(nil)} | ||||
| 	if err := cf(b).Write(&codec.Message{Type: codec.Publication}, p.Payload()); err != nil { | ||||
| 		return errors.InternalServerError("go.micro.client", err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	g.once.Do(func() { | ||||
| 		g.opts.Broker.Connect() | ||||
| 	}) | ||||
|  | ||||
| 	return g.opts.Broker.Publish(p.Topic(), &broker.Message{ | ||||
| 		Header: md, | ||||
| 		Body:   b.Bytes(), | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) String() string { | ||||
| 	return "grpc" | ||||
| } | ||||
|  | ||||
| func newClient(opts ...client.Option) client.Client { | ||||
| 	options := client.Options{ | ||||
| 		Codecs: make(map[string]codec.NewCodec), | ||||
| 		CallOptions: client.CallOptions{ | ||||
| 			Backoff:        client.DefaultBackoff, | ||||
| 			Retry:          client.DefaultRetry, | ||||
| 			Retries:        client.DefaultRetries, | ||||
| 			RequestTimeout: client.DefaultRequestTimeout, | ||||
| 			DialTimeout:    transport.DefaultDialTimeout, | ||||
| 		}, | ||||
| 		PoolSize: client.DefaultPoolSize, | ||||
| 		PoolTTL:  client.DefaultPoolTTL, | ||||
| 	} | ||||
|  | ||||
| 	for _, o := range opts { | ||||
| 		o(&options) | ||||
| 	} | ||||
|  | ||||
| 	if len(options.ContentType) == 0 { | ||||
| 		options.ContentType = "application/grpc+proto" | ||||
| 	} | ||||
|  | ||||
| 	if options.Broker == nil { | ||||
| 		options.Broker = broker.DefaultBroker | ||||
| 	} | ||||
|  | ||||
| 	if options.Registry == nil { | ||||
| 		options.Registry = registry.DefaultRegistry | ||||
| 	} | ||||
|  | ||||
| 	if options.Selector == nil { | ||||
| 		options.Selector = selector.NewSelector( | ||||
| 			selector.Registry(options.Registry), | ||||
| 		) | ||||
| 	} | ||||
|  | ||||
| 	rc := &grpcClient{ | ||||
| 		once: sync.Once{}, | ||||
| 		opts: options, | ||||
| 		pool: newPool(options.PoolSize, options.PoolTTL), | ||||
| 	} | ||||
|  | ||||
| 	c := client.Client(rc) | ||||
|  | ||||
| 	// wrap in reverse | ||||
| 	for i := len(options.Wrappers); i > 0; i-- { | ||||
| 		c = options.Wrappers[i-1](c) | ||||
| 	} | ||||
|  | ||||
| 	return c | ||||
| } | ||||
|  | ||||
| func NewClient(opts ...client.Option) client.Client { | ||||
| 	return newClient(opts...) | ||||
| } | ||||
							
								
								
									
										83
									
								
								client/grpc/grpc_pool.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								client/grpc/grpc_pool.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"google.golang.org/grpc" | ||||
| ) | ||||
|  | ||||
| type pool struct { | ||||
| 	size int | ||||
| 	ttl  int64 | ||||
|  | ||||
| 	sync.Mutex | ||||
| 	conns map[string][]*poolConn | ||||
| } | ||||
|  | ||||
| type poolConn struct { | ||||
| 	*grpc.ClientConn | ||||
| 	created int64 | ||||
| } | ||||
|  | ||||
| func newPool(size int, ttl time.Duration) *pool { | ||||
| 	return &pool{ | ||||
| 		size:  size, | ||||
| 		ttl:   int64(ttl.Seconds()), | ||||
| 		conns: make(map[string][]*poolConn), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *pool) getConn(addr string, opts ...grpc.DialOption) (*poolConn, error) { | ||||
| 	p.Lock() | ||||
| 	conns := p.conns[addr] | ||||
| 	now := time.Now().Unix() | ||||
|  | ||||
| 	// while we have conns check age and then return one | ||||
| 	// otherwise we'll create a new conn | ||||
| 	for len(conns) > 0 { | ||||
| 		conn := conns[len(conns)-1] | ||||
| 		conns = conns[:len(conns)-1] | ||||
| 		p.conns[addr] = conns | ||||
|  | ||||
| 		// if conn is old kill it and move on | ||||
| 		if d := now - conn.created; d > p.ttl { | ||||
| 			conn.ClientConn.Close() | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// we got a good conn, lets unlock and return it | ||||
| 		p.Unlock() | ||||
|  | ||||
| 		return conn, nil | ||||
| 	} | ||||
|  | ||||
| 	p.Unlock() | ||||
|  | ||||
| 	// create new conn | ||||
| 	cc, err := grpc.Dial(addr, opts...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &poolConn{cc, time.Now().Unix()}, nil | ||||
| } | ||||
|  | ||||
| func (p *pool) release(addr string, conn *poolConn, err error) { | ||||
| 	// don't store the conn if it has errored | ||||
| 	if err != nil { | ||||
| 		conn.ClientConn.Close() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// otherwise put it back for reuse | ||||
| 	p.Lock() | ||||
| 	conns := p.conns[addr] | ||||
| 	if len(conns) >= p.size { | ||||
| 		p.Unlock() | ||||
| 		conn.ClientConn.Close() | ||||
| 		return | ||||
| 	} | ||||
| 	p.conns[addr] = append(conns, conn) | ||||
| 	p.Unlock() | ||||
| } | ||||
							
								
								
									
										64
									
								
								client/grpc/grpc_pool_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								client/grpc/grpc_pool_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"net" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"context" | ||||
| 	"google.golang.org/grpc" | ||||
| 	pgrpc "google.golang.org/grpc" | ||||
| 	pb "google.golang.org/grpc/examples/helloworld/helloworld" | ||||
| ) | ||||
|  | ||||
| func testPool(t *testing.T, size int, ttl time.Duration) { | ||||
| 	// setup server | ||||
| 	l, err := net.Listen("tcp", ":0") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to listen: %v", err) | ||||
| 	} | ||||
| 	defer l.Close() | ||||
|  | ||||
| 	s := pgrpc.NewServer() | ||||
| 	pb.RegisterGreeterServer(s, &greeterServer{}) | ||||
|  | ||||
| 	go s.Serve(l) | ||||
| 	defer s.Stop() | ||||
|  | ||||
| 	// zero pool | ||||
| 	p := newPool(size, ttl) | ||||
|  | ||||
| 	for i := 0; i < 10; i++ { | ||||
| 		// get a conn | ||||
| 		cc, err := p.getConn(l.Addr().String(), grpc.WithInsecure()) | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		rsp := pb.HelloReply{} | ||||
|  | ||||
| 		err = cc.Invoke(context.TODO(), "/helloworld.Greeter/SayHello", &pb.HelloRequest{Name: "John"}, &rsp) | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		if rsp.Message != "Hello John" { | ||||
| 			t.Fatalf("Got unexpected response %v", rsp.Message) | ||||
| 		} | ||||
|  | ||||
| 		// release the conn | ||||
| 		p.release(l.Addr().String(), cc, nil) | ||||
|  | ||||
| 		p.Lock() | ||||
| 		if i := len(p.conns[l.Addr().String()]); i > size { | ||||
| 			p.Unlock() | ||||
| 			t.Fatalf("pool size %d is greater than expected %d", i, size) | ||||
| 		} | ||||
| 		p.Unlock() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestGRPCPool(t *testing.T) { | ||||
| 	testPool(t, 0, time.Minute) | ||||
| 	testPool(t, 2, time.Minute) | ||||
| } | ||||
							
								
								
									
										91
									
								
								client/grpc/grpc_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								client/grpc/grpc_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"net" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/micro/go-micro/client" | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/registry/memory" | ||||
| 	"github.com/micro/go-micro/selector" | ||||
| 	pgrpc "google.golang.org/grpc" | ||||
| 	pb "google.golang.org/grpc/examples/helloworld/helloworld" | ||||
| ) | ||||
|  | ||||
| // server is used to implement helloworld.GreeterServer. | ||||
| type greeterServer struct{} | ||||
|  | ||||
| // SayHello implements helloworld.GreeterServer | ||||
| func (g *greeterServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { | ||||
| 	return &pb.HelloReply{Message: "Hello " + in.Name}, nil | ||||
| } | ||||
|  | ||||
| func TestGRPCClient(t *testing.T) { | ||||
| 	l, err := net.Listen("tcp", ":0") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to listen: %v", err) | ||||
| 	} | ||||
| 	defer l.Close() | ||||
|  | ||||
| 	s := pgrpc.NewServer() | ||||
| 	pb.RegisterGreeterServer(s, &greeterServer{}) | ||||
|  | ||||
| 	go s.Serve(l) | ||||
| 	defer s.Stop() | ||||
|  | ||||
| 	parts := strings.Split(l.Addr().String(), ":") | ||||
| 	port, _ := strconv.Atoi(parts[len(parts)-1]) | ||||
| 	addr := strings.Join(parts[:len(parts)-1], ":") | ||||
|  | ||||
| 	// create mock registry | ||||
| 	r := memory.NewRegistry() | ||||
|  | ||||
| 	// register service | ||||
| 	r.Register(®istry.Service{ | ||||
| 		Name:    "test", | ||||
| 		Version: "test", | ||||
| 		Nodes: []*registry.Node{ | ||||
| 			®istry.Node{ | ||||
| 				Id:      "test-1", | ||||
| 				Address: addr, | ||||
| 				Port:    port, | ||||
| 			}, | ||||
| 		}, | ||||
| 	}) | ||||
|  | ||||
| 	// create selector | ||||
| 	se := selector.NewSelector( | ||||
| 		selector.Registry(r), | ||||
| 	) | ||||
|  | ||||
| 	// create client | ||||
| 	c := NewClient( | ||||
| 		client.Registry(r), | ||||
| 		client.Selector(se), | ||||
| 	) | ||||
|  | ||||
| 	testMethods := []string{ | ||||
| 		"/helloworld.Greeter/SayHello", | ||||
| 		"Greeter.SayHello", | ||||
| 	} | ||||
|  | ||||
| 	for _, method := range testMethods { | ||||
| 		req := c.NewRequest("test", method, &pb.HelloRequest{ | ||||
| 			Name: "John", | ||||
| 		}) | ||||
|  | ||||
| 		rsp := pb.HelloReply{} | ||||
|  | ||||
| 		err = c.Call(context.TODO(), req, &rsp) | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		if rsp.Message != "Hello John" { | ||||
| 			t.Fatalf("Got unexpected response %v", rsp.Message) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										40
									
								
								client/grpc/message.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								client/grpc/message.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"github.com/micro/go-micro/client" | ||||
| ) | ||||
|  | ||||
| type grpcPublication struct { | ||||
| 	topic       string | ||||
| 	contentType string | ||||
| 	payload     interface{} | ||||
| } | ||||
|  | ||||
| func newGRPCPublication(topic string, payload interface{}, contentType string, opts ...client.MessageOption) client.Message { | ||||
| 	var options client.MessageOptions | ||||
| 	for _, o := range opts { | ||||
| 		o(&options) | ||||
| 	} | ||||
|  | ||||
| 	if len(options.ContentType) > 0 { | ||||
| 		contentType = options.ContentType | ||||
| 	} | ||||
|  | ||||
| 	return &grpcPublication{ | ||||
| 		payload:     payload, | ||||
| 		topic:       topic, | ||||
| 		contentType: contentType, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (g *grpcPublication) ContentType() string { | ||||
| 	return g.contentType | ||||
| } | ||||
|  | ||||
| func (g *grpcPublication) Topic() string { | ||||
| 	return g.topic | ||||
| } | ||||
|  | ||||
| func (g *grpcPublication) Payload() interface{} { | ||||
| 	return g.payload | ||||
| } | ||||
							
								
								
									
										74
									
								
								client/grpc/options.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								client/grpc/options.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| // Package grpc provides a gRPC options | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
|  | ||||
| 	"github.com/micro/go-micro/client" | ||||
| 	"google.golang.org/grpc/encoding" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// DefaultMaxRecvMsgSize maximum message that client can receive | ||||
| 	// (4 MB). | ||||
| 	DefaultMaxRecvMsgSize = 1024 * 1024 * 4 | ||||
|  | ||||
| 	// DefaultMaxSendMsgSize maximum message that client can send | ||||
| 	// (4 MB). | ||||
| 	DefaultMaxSendMsgSize = 1024 * 1024 * 4 | ||||
| ) | ||||
|  | ||||
| type codecsKey struct{} | ||||
| type tlsAuth struct{} | ||||
| type maxRecvMsgSizeKey struct{} | ||||
| type maxSendMsgSizeKey struct{} | ||||
|  | ||||
| // gRPC Codec to be used to encode/decode requests for a given content type | ||||
| func Codec(contentType string, c encoding.Codec) client.Option { | ||||
| 	return func(o *client.Options) { | ||||
| 		codecs := make(map[string]encoding.Codec) | ||||
| 		if o.Context == nil { | ||||
| 			o.Context = context.Background() | ||||
| 		} | ||||
| 		if v := o.Context.Value(codecsKey{}); v != nil { | ||||
| 			codecs = v.(map[string]encoding.Codec) | ||||
| 		} | ||||
| 		codecs[contentType] = c | ||||
| 		o.Context = context.WithValue(o.Context, codecsKey{}, codecs) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // AuthTLS should be used to setup a secure authentication using TLS | ||||
| func AuthTLS(t *tls.Config) client.Option { | ||||
| 	return func(o *client.Options) { | ||||
| 		if o.Context == nil { | ||||
| 			o.Context = context.Background() | ||||
| 		} | ||||
| 		o.Context = context.WithValue(o.Context, tlsAuth{}, t) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // | ||||
| // MaxRecvMsgSize set the maximum size of message that client can receive. | ||||
| // | ||||
| func MaxRecvMsgSize(s int) client.Option { | ||||
| 	return func(o *client.Options) { | ||||
| 		if o.Context == nil { | ||||
| 			o.Context = context.Background() | ||||
| 		} | ||||
| 		o.Context = context.WithValue(o.Context, maxRecvMsgSizeKey{}, s) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // | ||||
| // MaxSendMsgSize set the maximum size of message that client can send. | ||||
| // | ||||
| func MaxSendMsgSize(s int) client.Option { | ||||
| 	return func(o *client.Options) { | ||||
| 		if o.Context == nil { | ||||
| 			o.Context = context.Background() | ||||
| 		} | ||||
| 		o.Context = context.WithValue(o.Context, maxSendMsgSizeKey{}, s) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										92
									
								
								client/grpc/request.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								client/grpc/request.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/micro/go-micro/client" | ||||
| 	"github.com/micro/go-micro/codec" | ||||
| ) | ||||
|  | ||||
| type grpcRequest struct { | ||||
| 	service     string | ||||
| 	method      string | ||||
| 	contentType string | ||||
| 	request     interface{} | ||||
| 	opts        client.RequestOptions | ||||
| } | ||||
|  | ||||
| func methodToGRPC(method string, request interface{}) string { | ||||
| 	// no method or already grpc method | ||||
| 	if len(method) == 0 || method[0] == '/' { | ||||
| 		return method | ||||
| 	} | ||||
| 	// can't operate on nil request | ||||
| 	t := reflect.TypeOf(request) | ||||
| 	if t == nil { | ||||
| 		return method | ||||
| 	} | ||||
| 	// dereference | ||||
| 	if t.Kind() == reflect.Ptr { | ||||
| 		t = t.Elem() | ||||
| 	} | ||||
| 	// get package name | ||||
| 	pParts := strings.Split(t.PkgPath(), "/") | ||||
| 	pkg := pParts[len(pParts)-1] | ||||
| 	// assume method is Foo.Bar | ||||
| 	mParts := strings.Split(method, ".") | ||||
| 	if len(mParts) != 2 { | ||||
| 		return method | ||||
| 	} | ||||
| 	// return /pkg.Foo/Bar | ||||
| 	return fmt.Sprintf("/%s.%s/%s", pkg, mParts[0], mParts[1]) | ||||
| } | ||||
|  | ||||
| func newGRPCRequest(service, method string, request interface{}, contentType string, reqOpts ...client.RequestOption) client.Request { | ||||
| 	var opts client.RequestOptions | ||||
| 	for _, o := range reqOpts { | ||||
| 		o(&opts) | ||||
| 	} | ||||
|  | ||||
| 	// set the content-type specified | ||||
| 	if len(opts.ContentType) > 0 { | ||||
| 		contentType = opts.ContentType | ||||
| 	} | ||||
|  | ||||
| 	return &grpcRequest{ | ||||
| 		service:     service, | ||||
| 		method:      method, | ||||
| 		request:     request, | ||||
| 		contentType: contentType, | ||||
| 		opts:        opts, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (g *grpcRequest) ContentType() string { | ||||
| 	return g.contentType | ||||
| } | ||||
|  | ||||
| func (g *grpcRequest) Service() string { | ||||
| 	return g.service | ||||
| } | ||||
|  | ||||
| func (g *grpcRequest) Method() string { | ||||
| 	return g.method | ||||
| } | ||||
|  | ||||
| func (g *grpcRequest) Endpoint() string { | ||||
| 	return g.method | ||||
| } | ||||
|  | ||||
| func (g *grpcRequest) Codec() codec.Writer { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (g *grpcRequest) Body() interface{} { | ||||
| 	return g.request | ||||
| } | ||||
|  | ||||
| func (g *grpcRequest) Stream() bool { | ||||
| 	return g.opts.Stream | ||||
| } | ||||
							
								
								
									
										48
									
								
								client/grpc/request_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								client/grpc/request_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	pb "google.golang.org/grpc/examples/helloworld/helloworld" | ||||
| ) | ||||
|  | ||||
| func TestMethodToGRPC(t *testing.T) { | ||||
| 	testData := []struct { | ||||
| 		method  string | ||||
| 		expect  string | ||||
| 		request interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			"Greeter.SayHello", | ||||
| 			"/helloworld.Greeter/SayHello", | ||||
| 			new(pb.HelloRequest), | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/helloworld.Greeter/SayHello", | ||||
| 			"/helloworld.Greeter/SayHello", | ||||
| 			new(pb.HelloRequest), | ||||
| 		}, | ||||
| 		{ | ||||
| 			"Greeter.SayHello", | ||||
| 			"/helloworld.Greeter/SayHello", | ||||
| 			pb.HelloRequest{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/helloworld.Greeter/SayHello", | ||||
| 			"/helloworld.Greeter/SayHello", | ||||
| 			pb.HelloRequest{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"Greeter.SayHello", | ||||
| 			"Greeter.SayHello", | ||||
| 			nil, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, d := range testData { | ||||
| 		method := methodToGRPC(d.method, d.request) | ||||
| 		if method != d.expect { | ||||
| 			t.Fatalf("expected %s got %s", d.expect, method) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										77
									
								
								client/grpc/stream.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								client/grpc/stream.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"io" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/micro/go-micro/client" | ||||
| 	"google.golang.org/grpc" | ||||
| ) | ||||
|  | ||||
| // Implements the streamer interface | ||||
| type grpcStream struct { | ||||
| 	sync.RWMutex | ||||
| 	err     error | ||||
| 	conn    *grpc.ClientConn | ||||
| 	request client.Request | ||||
| 	stream  grpc.ClientStream | ||||
| 	context context.Context | ||||
| } | ||||
|  | ||||
| func (g *grpcStream) Context() context.Context { | ||||
| 	return g.context | ||||
| } | ||||
|  | ||||
| func (g *grpcStream) Request() client.Request { | ||||
| 	return g.request | ||||
| } | ||||
|  | ||||
| func (g *grpcStream) Response() client.Response { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (g *grpcStream) Send(msg interface{}) error { | ||||
| 	if err := g.stream.SendMsg(msg); err != nil { | ||||
| 		g.setError(err) | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (g *grpcStream) Recv(msg interface{}) (err error) { | ||||
| 	defer g.setError(err) | ||||
| 	if err = g.stream.RecvMsg(msg); err != nil { | ||||
| 		if err == io.EOF { | ||||
| 			// #202 - inconsistent gRPC stream behavior | ||||
| 			// the only way to tell if the stream is done is when we get a EOF on the Recv | ||||
| 			// here we should close the underlying gRPC ClientConn | ||||
| 			closeErr := g.conn.Close() | ||||
| 			if closeErr != nil { | ||||
| 				err = closeErr | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (g *grpcStream) Error() error { | ||||
| 	g.RLock() | ||||
| 	defer g.RUnlock() | ||||
| 	return g.err | ||||
| } | ||||
|  | ||||
| func (g *grpcStream) setError(e error) { | ||||
| 	g.Lock() | ||||
| 	g.err = e | ||||
| 	g.Unlock() | ||||
| } | ||||
|  | ||||
| // Close the gRPC send stream | ||||
| // #202 - inconsistent gRPC stream behavior | ||||
| // The underlying gRPC stream should not be closed here since the | ||||
| // stream should still be able to receive after this function call | ||||
| // TODO: should the conn be closed in another way? | ||||
| func (g *grpcStream) Close() error { | ||||
| 	return g.stream.CloseSend() | ||||
| } | ||||
							
								
								
									
										102
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								go.mod
									
									
									
									
									
								
							| @@ -1,119 +1,55 @@ | ||||
| module github.com/micro/go-micro | ||||
|  | ||||
| require ( | ||||
| 	cloud.google.com/go v0.39.0 // indirect | ||||
| 	github.com/BurntSushi/toml v0.3.1 | ||||
| 	github.com/OneOfOne/xxhash v1.2.5 // indirect | ||||
| 	github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect | ||||
| 	github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect | ||||
| 	github.com/armon/go-radix v1.0.0 // indirect | ||||
| 	github.com/beevik/ntp v0.2.0 | ||||
| 	github.com/bitly/go-simplejson v0.5.0 | ||||
| 	github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect | ||||
| 	github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 | ||||
| 	github.com/bwmarrin/discordgo v0.19.0 | ||||
| 	github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc // indirect | ||||
| 	github.com/coreos/bbolt v1.3.2 // indirect | ||||
| 	github.com/coreos/etcd v3.3.13+incompatible | ||||
| 	github.com/coreos/go-semver v0.3.0 // indirect | ||||
| 	github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect | ||||
| 	github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect | ||||
| 	github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect | ||||
| 	github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b // indirect | ||||
| 	github.com/emirpasic/gods v1.12.0 // indirect | ||||
| 	github.com/forestgiant/sliceutil v0.0.0-20160425183142-94783f95db6c | ||||
| 	github.com/fsnotify/fsnotify v1.4.7 | ||||
| 	github.com/fsouza/go-dockerclient v1.4.1 | ||||
| 	github.com/garyburd/redigo v1.6.0 // indirect | ||||
| 	github.com/ghodss/yaml v1.0.0 | ||||
| 	github.com/gliderlabs/ssh v0.1.4 // indirect | ||||
| 	github.com/go-log/log v0.1.0 | ||||
| 	github.com/go-redsync/redsync v1.2.0 | ||||
| 	github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect | ||||
| 	github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect | ||||
| 	github.com/golang/mock v1.3.1 // indirect | ||||
| 	github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b | ||||
| 	github.com/golang/protobuf v1.3.1 | ||||
| 	github.com/gomodule/redigo v2.0.0+incompatible | ||||
| 	github.com/google/btree v1.0.0 // indirect | ||||
| 	github.com/google/pprof v0.0.0-20190515194954-54271f7e092f // indirect | ||||
| 	github.com/google/uuid v1.1.1 | ||||
| 	github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect | ||||
| 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect | ||||
| 	github.com/grpc-ecosystem/grpc-gateway v1.9.0 // indirect | ||||
| 	github.com/gorilla/handlers v1.4.0 | ||||
| 	github.com/gorilla/websocket v1.4.0 | ||||
| 	github.com/grpc-ecosystem/grpc-gateway v1.9.0 | ||||
| 	github.com/hashicorp/consul v1.5.1 | ||||
| 	github.com/hashicorp/consul/api v1.1.0 | ||||
| 	github.com/hashicorp/go-immutable-radix v1.1.0 // indirect | ||||
| 	github.com/hashicorp/go-msgpack v0.5.5 // indirect | ||||
| 	github.com/hashicorp/go-sockaddr v1.0.2 // indirect | ||||
| 	github.com/hashicorp/hcl v1.0.0 | ||||
| 	github.com/hashicorp/mdns v1.0.1 // indirect | ||||
| 	github.com/hashicorp/memberlist v0.1.4 | ||||
| 	github.com/hashicorp/serf v0.8.3 // indirect | ||||
| 	github.com/imdario/mergo v0.3.7 | ||||
| 	github.com/jonboulle/clockwork v0.1.0 // indirect | ||||
| 	github.com/json-iterator/go v1.1.6 // indirect | ||||
| 	github.com/kisielk/errcheck v1.2.0 // indirect | ||||
| 	github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect | ||||
| 	github.com/kr/pty v1.1.4 // indirect | ||||
| 	github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect | ||||
| 	github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018 // indirect | ||||
| 	github.com/mattn/go-colorable v0.1.2 // indirect | ||||
| 	github.com/joncalhoun/qson v0.0.0-20170526102502-8a9cab3a62b1 | ||||
| 	github.com/json-iterator/go v1.1.6 | ||||
| 	github.com/micro/cli v0.2.0 | ||||
| 	github.com/micro/examples v0.1.0 | ||||
| 	github.com/micro/go-plugins v1.1.0 | ||||
| 	github.com/micro/mdns v0.1.0 | ||||
| 	github.com/miekg/dns v1.1.13 // indirect | ||||
| 	github.com/mitchellh/go-homedir v1.1.0 // indirect | ||||
| 	github.com/micro/micro v1.3.0 | ||||
| 	github.com/mitchellh/hashstructure v1.0.0 | ||||
| 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||||
| 	github.com/modern-go/reflect2 v1.0.1 // indirect | ||||
| 	github.com/nats-io/gnatsd v1.4.1 // indirect | ||||
| 	github.com/nats-io/go-nats v1.7.2 // indirect | ||||
| 	github.com/nats-io/nats.go v1.7.2 | ||||
| 	github.com/nats-io/nkeys v0.0.2 // indirect | ||||
| 	github.com/nats-io/nuid v1.0.1 // indirect | ||||
| 	github.com/nlopes/slack v0.5.0 | ||||
| 	github.com/onsi/ginkgo v1.8.0 // indirect | ||||
| 	github.com/onsi/gomega v1.5.0 // indirect | ||||
| 	github.com/pborman/uuid v1.2.0 | ||||
| 	github.com/pkg/errors v0.8.1 | ||||
| 	github.com/posener/complete v1.2.1 // indirect | ||||
| 	github.com/prometheus/client_golang v0.9.3 // indirect | ||||
| 	github.com/prometheus/common v0.4.1 // indirect | ||||
| 	github.com/prometheus/procfs v0.0.1 // indirect | ||||
| 	github.com/prometheus/tsdb v0.8.0 // indirect | ||||
| 	github.com/rogpeppe/fastuuid v1.1.0 // indirect | ||||
| 	github.com/sirupsen/logrus v1.4.2 // indirect | ||||
| 	github.com/soheilhy/cmux v0.1.4 // indirect | ||||
| 	github.com/spaolacci/murmur3 v1.1.0 // indirect | ||||
| 	github.com/stretchr/objx v0.2.0 // indirect | ||||
| 	github.com/technoweenie/multipartstreamer v1.0.1 // indirect | ||||
| 	github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect | ||||
| 	github.com/xanzy/ssh-agent v0.2.1 // indirect | ||||
| 	github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect | ||||
| 	go.etcd.io/bbolt v1.3.2 // indirect | ||||
| 	go.etcd.io/etcd v3.3.13+incompatible | ||||
| 	go.opencensus.io v0.22.0 // indirect | ||||
| 	go.uber.org/atomic v1.4.0 // indirect | ||||
| 	go.uber.org/multierr v1.1.0 // indirect | ||||
| 	go.uber.org/zap v1.10.0 // indirect | ||||
| 	golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 // indirect | ||||
| 	golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522 // indirect | ||||
| 	golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff // indirect | ||||
| 	golang.org/x/lint v0.0.0-20190409202823-959b441ac422 // indirect | ||||
| 	golang.org/x/mobile v0.0.0-20190509164839-32b2708ab171 // indirect | ||||
| 	golang.org/x/net v0.0.0-20190522155817-f3200d17e092 | ||||
| 	golang.org/x/oauth2 v0.0.0-20190523182746-aaccbc9213b0 // indirect | ||||
| 	golang.org/x/sys v0.0.0-20190531132440-69e3a3a65b5b // indirect | ||||
| 	golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect | ||||
| 	golang.org/x/tools v0.0.0-20190530215528-75312fb06703 // indirect | ||||
| 	google.golang.org/appengine v1.6.0 // indirect | ||||
| 	google.golang.org/genproto v0.0.0-20190530194941-fb225487d101 // indirect | ||||
| 	google.golang.org/grpc v1.21.0 // indirect | ||||
| 	gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a // indirect | ||||
| 	golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 | ||||
| 	golang.org/x/mod v0.1.0 // indirect | ||||
| 	golang.org/x/net v0.0.0-20190603091049-60506f45cf65 | ||||
| 	golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed // indirect | ||||
| 	golang.org/x/tools v0.0.0-20190603152906-08e0b306e832 // indirect | ||||
| 	google.golang.org/genproto v0.0.0-20190530194941-fb225487d101 | ||||
| 	google.golang.org/grpc v1.21.0 | ||||
| 	gopkg.in/go-playground/validator.v9 v9.29.0 | ||||
| 	gopkg.in/redis.v3 v3.6.4 | ||||
| 	gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect | ||||
| 	gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 // indirect | ||||
| 	gopkg.in/src-d/go-git.v4 v4.11.0 | ||||
| 	gopkg.in/telegram-bot-api.v4 v4.6.4 | ||||
| 	honnef.co/go/tools v0.0.0-20190530170028-a1efa522b896 // indirect | ||||
| 	honnef.co/go/tools v0.0.0-20190602125119-5a4a2f4a438d // indirect | ||||
| ) | ||||
|  | ||||
| exclude sourcegraph.com/sourcegraph/go-diff v0.5.1 | ||||
|   | ||||
							
								
								
									
										25
									
								
								proxy/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								proxy/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| # Go Proxy [](https://opensource.org/licenses/Apache-2.0) [](https://godoc.org/github.com/micro/go-proxy) | ||||
|  | ||||
| Go Proxy is a proxy library for Go Micro. | ||||
|  | ||||
| ## Overview | ||||
|  | ||||
| Go Micro is a distributed systems framework for client/server communication. It handles the details  | ||||
| around discovery, fault tolerance, rpc communication, etc. We may want to leverage this in broader ecosystems  | ||||
| which make use of standard http or we may also want to offload a number of requirements to a single proxy. | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| - **Transparent Proxy** - Proxy requests to any micro services through a single location. Go Proxy enables  | ||||
| you to write Go Micro proxies which handle and forward requests. This is good for incorporating wrappers. | ||||
|  | ||||
| - **Single Backend Router** - Enable the single backend router to proxy directly to your local app. The proxy  | ||||
| allows you to set a router which serves your backend service whether its http, grpc, etc. | ||||
|  | ||||
| - **Protocol Aware Handler** - Set a request handler which speaks your app protocol to make outbound requests.  | ||||
| Your app may not speak the MUCP protocol so it may be easier to translate internally. | ||||
|  | ||||
| - **Control Planes** - Additionally we support use of control planes to offload many distributed systems concerns. | ||||
|   * [x] [Consul](https://www.consul.io/docs/connect/native.html) - Using Connect-Native to provide secure mTLS. | ||||
|   * [x] [NATS](https://nats.io/) - Fully leveraging NATS as the control plane and data plane. | ||||
|  | ||||
							
								
								
									
										77
									
								
								proxy/control/consul/consul.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								proxy/control/consul/consul.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| // Package consul provides Consul Connect control plane | ||||
| package consul | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
|  | ||||
| 	"github.com/hashicorp/consul/api" | ||||
| 	"github.com/hashicorp/consul/connect" | ||||
| 	"github.com/micro/go-micro" | ||||
| 	"github.com/micro/go-micro/broker" | ||||
| 	"github.com/micro/go-micro/registry/consul" | ||||
| 	"github.com/micro/go-micro/transport" | ||||
| ) | ||||
|  | ||||
| type proxyService struct { | ||||
| 	c *connect.Service | ||||
| 	micro.Service | ||||
| } | ||||
|  | ||||
| func newService(opts ...micro.Option) micro.Service { | ||||
| 	// we need to use the consul registry to register connect applications | ||||
| 	r := consul.NewRegistry( | ||||
| 		consul.Connect(), | ||||
| 	) | ||||
|  | ||||
| 	// pass in the registry as part of our options | ||||
| 	newOpts := append([]micro.Option{micro.Registry(r)}, opts...) | ||||
|  | ||||
| 	// service := micro.NewService(newOpts...) | ||||
| 	service := micro.NewService(newOpts...) | ||||
|  | ||||
| 	// get the consul address | ||||
| 	addrs := service.Server().Options().Registry.Options().Addrs | ||||
|  | ||||
| 	// set the config | ||||
| 	config := api.DefaultConfig() | ||||
| 	if len(addrs) > 0 { | ||||
| 		config.Address = addrs[0] | ||||
| 	} | ||||
|  | ||||
| 	// create consul client | ||||
| 	client, err := api.NewClient(api.DefaultConfig()) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// create connect service using the service name | ||||
| 	svc, err := connect.NewService(service.Server().Options().Name, client) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// setup transport tls config | ||||
| 	service.Options().Transport.Init( | ||||
| 		transport.TLSConfig(svc.ServerTLSConfig()), | ||||
| 	) | ||||
|  | ||||
| 	// setup broker tls config | ||||
| 	service.Options().Broker.Init( | ||||
| 		broker.TLSConfig(svc.ServerTLSConfig()), | ||||
| 	) | ||||
|  | ||||
| 	// return a new proxy enabled service | ||||
| 	return &proxyService{ | ||||
| 		c:       svc, | ||||
| 		Service: service, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *proxyService) String() string { | ||||
| 	return "consul" | ||||
| } | ||||
|  | ||||
| // NewService returns a Consul Connect-Native micro.Service | ||||
| func NewService(opts ...micro.Option) micro.Service { | ||||
| 	return newService(opts...) | ||||
| } | ||||
							
								
								
									
										30
									
								
								proxy/control/nats/nats.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								proxy/control/nats/nats.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| // Package nats provides a NATS control plane | ||||
| package nats | ||||
|  | ||||
| import ( | ||||
| 	"github.com/micro/go-micro" | ||||
| 	broker "github.com/micro/go-plugins/broker/nats" | ||||
| 	registry "github.com/micro/go-plugins/registry/nats" | ||||
| 	transport "github.com/micro/go-plugins/transport/nats" | ||||
| ) | ||||
|  | ||||
| // NewService returns a NATS micro.Service | ||||
| func NewService(opts ...micro.Option) micro.Service { | ||||
| 	// initialise nats components | ||||
| 	b := broker.NewBroker() | ||||
| 	r := registry.NewRegistry() | ||||
| 	t := transport.NewTransport() | ||||
|  | ||||
| 	// create new options | ||||
| 	options := []micro.Option{ | ||||
| 		micro.Broker(b), | ||||
| 		micro.Registry(r), | ||||
| 		micro.Transport(t), | ||||
| 	} | ||||
|  | ||||
| 	// append user options | ||||
| 	options = append(options, opts...) | ||||
|  | ||||
| 	// return a nats service | ||||
| 	return micro.NewService(options...) | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								proxy/proto/.proxy.proto.swp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								proxy/proto/.proxy.proto.swp
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										207
									
								
								proxy/proto/proxy.micro.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								proxy/proto/proxy.micro.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | ||||
| // Code generated by protoc-gen-micro. DO NOT EDIT. | ||||
| // source: github.com/micro/go-proxy/proto/proxy.proto | ||||
|  | ||||
| /* | ||||
| Package proxy is a generated protocol buffer package. | ||||
|  | ||||
| It is generated from these files: | ||||
| 	github.com/micro/go-proxy/proto/proxy.proto | ||||
|  | ||||
| It has these top-level messages: | ||||
| 	Request | ||||
| 	Response | ||||
| 	Message | ||||
| 	Empty | ||||
| */ | ||||
| package proxy | ||||
|  | ||||
| import proto "github.com/golang/protobuf/proto" | ||||
| import fmt "fmt" | ||||
| import math "math" | ||||
|  | ||||
| import ( | ||||
| 	context "context" | ||||
| 	client "github.com/micro/go-micro/client" | ||||
| 	server "github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| // Reference imports to suppress errors if they are not otherwise used. | ||||
| var _ = proto.Marshal | ||||
| var _ = fmt.Errorf | ||||
| var _ = math.Inf | ||||
|  | ||||
| // This is a compile-time assertion to ensure that this generated file | ||||
| // is compatible with the proto package it is being compiled against. | ||||
| // A compilation error at this line likely means your copy of the | ||||
| // proto package needs to be updated. | ||||
| const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package | ||||
|  | ||||
| // Reference imports to suppress errors if they are not otherwise used. | ||||
| var _ context.Context | ||||
| var _ client.Option | ||||
| var _ server.Option | ||||
|  | ||||
| // Client API for Service service | ||||
|  | ||||
| type Service interface { | ||||
| 	Call(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) | ||||
| 	Stream(ctx context.Context, opts ...client.CallOption) (Service_StreamService, error) | ||||
| 	Publish(ctx context.Context, in *Message, opts ...client.CallOption) (*Empty, error) | ||||
| } | ||||
|  | ||||
| type service struct { | ||||
| 	c    client.Client | ||||
| 	name string | ||||
| } | ||||
|  | ||||
| func NewService(name string, c client.Client) Service { | ||||
| 	if c == nil { | ||||
| 		c = client.NewClient() | ||||
| 	} | ||||
| 	if len(name) == 0 { | ||||
| 		name = "proxy" | ||||
| 	} | ||||
| 	return &service{ | ||||
| 		c:    c, | ||||
| 		name: name, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *service) Call(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) { | ||||
| 	req := c.c.NewRequest(c.name, "Service.Call", in) | ||||
| 	out := new(Response) | ||||
| 	err := c.c.Call(ctx, req, out, opts...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| func (c *service) Stream(ctx context.Context, opts ...client.CallOption) (Service_StreamService, error) { | ||||
| 	req := c.c.NewRequest(c.name, "Service.Stream", &Request{}) | ||||
| 	stream, err := c.c.Stream(ctx, req, opts...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &serviceStream{stream}, nil | ||||
| } | ||||
|  | ||||
| type Service_StreamService interface { | ||||
| 	SendMsg(interface{}) error | ||||
| 	RecvMsg(interface{}) error | ||||
| 	Close() error | ||||
| 	Send(*Request) error | ||||
| 	Recv() (*Response, error) | ||||
| } | ||||
|  | ||||
| type serviceStream struct { | ||||
| 	stream client.Stream | ||||
| } | ||||
|  | ||||
| func (x *serviceStream) Close() error { | ||||
| 	return x.stream.Close() | ||||
| } | ||||
|  | ||||
| func (x *serviceStream) SendMsg(m interface{}) error { | ||||
| 	return x.stream.Send(m) | ||||
| } | ||||
|  | ||||
| func (x *serviceStream) RecvMsg(m interface{}) error { | ||||
| 	return x.stream.Recv(m) | ||||
| } | ||||
|  | ||||
| func (x *serviceStream) Send(m *Request) error { | ||||
| 	return x.stream.Send(m) | ||||
| } | ||||
|  | ||||
| func (x *serviceStream) Recv() (*Response, error) { | ||||
| 	m := new(Response) | ||||
| 	err := x.stream.Recv(m) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return m, nil | ||||
| } | ||||
|  | ||||
| func (c *service) Publish(ctx context.Context, in *Message, opts ...client.CallOption) (*Empty, error) { | ||||
| 	req := c.c.NewRequest(c.name, "Service.Publish", in) | ||||
| 	out := new(Empty) | ||||
| 	err := c.c.Call(ctx, req, out, opts...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| // Server API for Service service | ||||
|  | ||||
| type ServiceHandler interface { | ||||
| 	Call(context.Context, *Request, *Response) error | ||||
| 	Stream(context.Context, Service_StreamStream) error | ||||
| 	Publish(context.Context, *Message, *Empty) error | ||||
| } | ||||
|  | ||||
| func RegisterServiceHandler(s server.Server, hdlr ServiceHandler, opts ...server.HandlerOption) error { | ||||
| 	type service interface { | ||||
| 		Call(ctx context.Context, in *Request, out *Response) error | ||||
| 		Stream(ctx context.Context, stream server.Stream) error | ||||
| 		Publish(ctx context.Context, in *Message, out *Empty) error | ||||
| 	} | ||||
| 	type Service struct { | ||||
| 		service | ||||
| 	} | ||||
| 	h := &serviceHandler{hdlr} | ||||
| 	return s.Handle(s.NewHandler(&Service{h}, opts...)) | ||||
| } | ||||
|  | ||||
| type serviceHandler struct { | ||||
| 	ServiceHandler | ||||
| } | ||||
|  | ||||
| func (h *serviceHandler) Call(ctx context.Context, in *Request, out *Response) error { | ||||
| 	return h.ServiceHandler.Call(ctx, in, out) | ||||
| } | ||||
|  | ||||
| func (h *serviceHandler) Stream(ctx context.Context, stream server.Stream) error { | ||||
| 	return h.ServiceHandler.Stream(ctx, &serviceStreamStream{stream}) | ||||
| } | ||||
|  | ||||
| type Service_StreamStream interface { | ||||
| 	SendMsg(interface{}) error | ||||
| 	RecvMsg(interface{}) error | ||||
| 	Close() error | ||||
| 	Send(*Response) error | ||||
| 	Recv() (*Request, error) | ||||
| } | ||||
|  | ||||
| type serviceStreamStream struct { | ||||
| 	stream server.Stream | ||||
| } | ||||
|  | ||||
| func (x *serviceStreamStream) Close() error { | ||||
| 	return x.stream.Close() | ||||
| } | ||||
|  | ||||
| func (x *serviceStreamStream) SendMsg(m interface{}) error { | ||||
| 	return x.stream.Send(m) | ||||
| } | ||||
|  | ||||
| func (x *serviceStreamStream) RecvMsg(m interface{}) error { | ||||
| 	return x.stream.Recv(m) | ||||
| } | ||||
|  | ||||
| func (x *serviceStreamStream) Send(m *Response) error { | ||||
| 	return x.stream.Send(m) | ||||
| } | ||||
|  | ||||
| func (x *serviceStreamStream) Recv() (*Request, error) { | ||||
| 	m := new(Request) | ||||
| 	if err := x.stream.Recv(m); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return m, nil | ||||
| } | ||||
|  | ||||
| func (h *serviceHandler) Publish(ctx context.Context, in *Message, out *Empty) error { | ||||
| 	return h.ServiceHandler.Publish(ctx, in, out) | ||||
| } | ||||
							
								
								
									
										345
									
								
								proxy/proto/proxy.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										345
									
								
								proxy/proto/proxy.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,345 @@ | ||||
| // Code generated by protoc-gen-go. DO NOT EDIT. | ||||
| // source: github.com/micro/go-proxy/proto/proxy.proto | ||||
|  | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	context "context" | ||||
| 	fmt "fmt" | ||||
| 	proto "github.com/golang/protobuf/proto" | ||||
| 	grpc "google.golang.org/grpc" | ||||
| 	math "math" | ||||
| ) | ||||
|  | ||||
| // Reference imports to suppress errors if they are not otherwise used. | ||||
| var _ = proto.Marshal | ||||
| var _ = fmt.Errorf | ||||
| var _ = math.Inf | ||||
|  | ||||
| // This is a compile-time assertion to ensure that this generated file | ||||
| // is compatible with the proto package it is being compiled against. | ||||
| // A compilation error at this line likely means your copy of the | ||||
| // proto package needs to be updated. | ||||
| const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package | ||||
|  | ||||
| type Request struct { | ||||
| 	XXX_NoUnkeyedLiteral struct{} `json:"-"` | ||||
| 	XXX_unrecognized     []byte   `json:"-"` | ||||
| 	XXX_sizecache        int32    `json:"-"` | ||||
| } | ||||
|  | ||||
| func (m *Request) Reset()         { *m = Request{} } | ||||
| func (m *Request) String() string { return proto.CompactTextString(m) } | ||||
| func (*Request) ProtoMessage()    {} | ||||
| func (*Request) Descriptor() ([]byte, []int) { | ||||
| 	return fileDescriptor_242fe7b9d67fe7d7, []int{0} | ||||
| } | ||||
|  | ||||
| func (m *Request) XXX_Unmarshal(b []byte) error { | ||||
| 	return xxx_messageInfo_Request.Unmarshal(m, b) | ||||
| } | ||||
| func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { | ||||
| 	return xxx_messageInfo_Request.Marshal(b, m, deterministic) | ||||
| } | ||||
| func (m *Request) XXX_Merge(src proto.Message) { | ||||
| 	xxx_messageInfo_Request.Merge(m, src) | ||||
| } | ||||
| func (m *Request) XXX_Size() int { | ||||
| 	return xxx_messageInfo_Request.Size(m) | ||||
| } | ||||
| func (m *Request) XXX_DiscardUnknown() { | ||||
| 	xxx_messageInfo_Request.DiscardUnknown(m) | ||||
| } | ||||
|  | ||||
| var xxx_messageInfo_Request proto.InternalMessageInfo | ||||
|  | ||||
| type Response struct { | ||||
| 	XXX_NoUnkeyedLiteral struct{} `json:"-"` | ||||
| 	XXX_unrecognized     []byte   `json:"-"` | ||||
| 	XXX_sizecache        int32    `json:"-"` | ||||
| } | ||||
|  | ||||
| func (m *Response) Reset()         { *m = Response{} } | ||||
| func (m *Response) String() string { return proto.CompactTextString(m) } | ||||
| func (*Response) ProtoMessage()    {} | ||||
| func (*Response) Descriptor() ([]byte, []int) { | ||||
| 	return fileDescriptor_242fe7b9d67fe7d7, []int{1} | ||||
| } | ||||
|  | ||||
| func (m *Response) XXX_Unmarshal(b []byte) error { | ||||
| 	return xxx_messageInfo_Response.Unmarshal(m, b) | ||||
| } | ||||
| func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { | ||||
| 	return xxx_messageInfo_Response.Marshal(b, m, deterministic) | ||||
| } | ||||
| func (m *Response) XXX_Merge(src proto.Message) { | ||||
| 	xxx_messageInfo_Response.Merge(m, src) | ||||
| } | ||||
| func (m *Response) XXX_Size() int { | ||||
| 	return xxx_messageInfo_Response.Size(m) | ||||
| } | ||||
| func (m *Response) XXX_DiscardUnknown() { | ||||
| 	xxx_messageInfo_Response.DiscardUnknown(m) | ||||
| } | ||||
|  | ||||
| var xxx_messageInfo_Response proto.InternalMessageInfo | ||||
|  | ||||
| type Message struct { | ||||
| 	XXX_NoUnkeyedLiteral struct{} `json:"-"` | ||||
| 	XXX_unrecognized     []byte   `json:"-"` | ||||
| 	XXX_sizecache        int32    `json:"-"` | ||||
| } | ||||
|  | ||||
| func (m *Message) Reset()         { *m = Message{} } | ||||
| func (m *Message) String() string { return proto.CompactTextString(m) } | ||||
| func (*Message) ProtoMessage()    {} | ||||
| func (*Message) Descriptor() ([]byte, []int) { | ||||
| 	return fileDescriptor_242fe7b9d67fe7d7, []int{2} | ||||
| } | ||||
|  | ||||
| func (m *Message) XXX_Unmarshal(b []byte) error { | ||||
| 	return xxx_messageInfo_Message.Unmarshal(m, b) | ||||
| } | ||||
| func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { | ||||
| 	return xxx_messageInfo_Message.Marshal(b, m, deterministic) | ||||
| } | ||||
| func (m *Message) XXX_Merge(src proto.Message) { | ||||
| 	xxx_messageInfo_Message.Merge(m, src) | ||||
| } | ||||
| func (m *Message) XXX_Size() int { | ||||
| 	return xxx_messageInfo_Message.Size(m) | ||||
| } | ||||
| func (m *Message) XXX_DiscardUnknown() { | ||||
| 	xxx_messageInfo_Message.DiscardUnknown(m) | ||||
| } | ||||
|  | ||||
| var xxx_messageInfo_Message proto.InternalMessageInfo | ||||
|  | ||||
| type Empty struct { | ||||
| 	XXX_NoUnkeyedLiteral struct{} `json:"-"` | ||||
| 	XXX_unrecognized     []byte   `json:"-"` | ||||
| 	XXX_sizecache        int32    `json:"-"` | ||||
| } | ||||
|  | ||||
| func (m *Empty) Reset()         { *m = Empty{} } | ||||
| func (m *Empty) String() string { return proto.CompactTextString(m) } | ||||
| func (*Empty) ProtoMessage()    {} | ||||
| func (*Empty) Descriptor() ([]byte, []int) { | ||||
| 	return fileDescriptor_242fe7b9d67fe7d7, []int{3} | ||||
| } | ||||
|  | ||||
| func (m *Empty) XXX_Unmarshal(b []byte) error { | ||||
| 	return xxx_messageInfo_Empty.Unmarshal(m, b) | ||||
| } | ||||
| func (m *Empty) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { | ||||
| 	return xxx_messageInfo_Empty.Marshal(b, m, deterministic) | ||||
| } | ||||
| func (m *Empty) XXX_Merge(src proto.Message) { | ||||
| 	xxx_messageInfo_Empty.Merge(m, src) | ||||
| } | ||||
| func (m *Empty) XXX_Size() int { | ||||
| 	return xxx_messageInfo_Empty.Size(m) | ||||
| } | ||||
| func (m *Empty) XXX_DiscardUnknown() { | ||||
| 	xxx_messageInfo_Empty.DiscardUnknown(m) | ||||
| } | ||||
|  | ||||
| var xxx_messageInfo_Empty proto.InternalMessageInfo | ||||
|  | ||||
| func init() { | ||||
| 	proto.RegisterType((*Request)(nil), "proxy.Request") | ||||
| 	proto.RegisterType((*Response)(nil), "proxy.Response") | ||||
| 	proto.RegisterType((*Message)(nil), "proxy.Message") | ||||
| 	proto.RegisterType((*Empty)(nil), "proxy.Empty") | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	proto.RegisterFile("github.com/micro/go-proxy/proto/proxy.proto", fileDescriptor_242fe7b9d67fe7d7) | ||||
| } | ||||
|  | ||||
| var fileDescriptor_242fe7b9d67fe7d7 = []byte{ | ||||
| 	// 186 bytes of a gzipped FileDescriptorProto | ||||
| 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x4e, 0xcf, 0x2c, 0xc9, | ||||
| 	0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0xcf, 0xcd, 0x4c, 0x2e, 0xca, 0xd7, 0x4f, 0xcf, 0xd7, | ||||
| 	0x2d, 0x28, 0xca, 0xaf, 0xa8, 0xd4, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0xd7, 0x07, 0xb3, 0xf5, 0xc0, | ||||
| 	0x6c, 0x21, 0x56, 0x30, 0x47, 0x89, 0x93, 0x8b, 0x3d, 0x28, 0xb5, 0xb0, 0x34, 0xb5, 0xb8, 0x44, | ||||
| 	0x89, 0x8b, 0x8b, 0x23, 0x28, 0xb5, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x15, 0x24, 0xec, 0x9b, 0x5a, | ||||
| 	0x5c, 0x9c, 0x98, 0x9e, 0xaa, 0xc4, 0xce, 0xc5, 0xea, 0x9a, 0x5b, 0x50, 0x52, 0x69, 0x34, 0x81, | ||||
| 	0x91, 0x8b, 0x3d, 0x38, 0xb5, 0xa8, 0x2c, 0x33, 0x39, 0x55, 0x48, 0x93, 0x8b, 0xc5, 0x39, 0x31, | ||||
| 	0x27, 0x47, 0x88, 0x4f, 0x0f, 0x62, 0x26, 0xd4, 0x0c, 0x29, 0x7e, 0x38, 0x1f, 0x6a, 0x10, 0x83, | ||||
| 	0x90, 0x3e, 0x17, 0x5b, 0x70, 0x49, 0x51, 0x6a, 0x62, 0x2e, 0x11, 0x8a, 0x35, 0x18, 0x0d, 0x18, | ||||
| 	0x85, 0x34, 0xb9, 0xd8, 0x03, 0x4a, 0x93, 0x72, 0x32, 0x8b, 0x33, 0xe0, 0x3a, 0xa0, 0x6e, 0x91, | ||||
| 	0xe2, 0x81, 0xf2, 0xc1, 0x0e, 0x52, 0x62, 0x48, 0x62, 0x03, 0xfb, 0xc5, 0x18, 0x10, 0x00, 0x00, | ||||
| 	0xff, 0xff, 0x51, 0x0d, 0x40, 0x94, 0xfa, 0x00, 0x00, 0x00, | ||||
| } | ||||
|  | ||||
| // Reference imports to suppress errors if they are not otherwise used. | ||||
| var _ context.Context | ||||
| var _ grpc.ClientConn | ||||
|  | ||||
| // This is a compile-time assertion to ensure that this generated file | ||||
| // is compatible with the grpc package it is being compiled against. | ||||
| const _ = grpc.SupportPackageIsVersion4 | ||||
|  | ||||
| // ServiceClient is the client API for Service service. | ||||
| // | ||||
| // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. | ||||
| type ServiceClient interface { | ||||
| 	Call(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) | ||||
| 	Stream(ctx context.Context, opts ...grpc.CallOption) (Service_StreamClient, error) | ||||
| 	Publish(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Empty, error) | ||||
| } | ||||
|  | ||||
| type serviceClient struct { | ||||
| 	cc *grpc.ClientConn | ||||
| } | ||||
|  | ||||
| func NewServiceClient(cc *grpc.ClientConn) ServiceClient { | ||||
| 	return &serviceClient{cc} | ||||
| } | ||||
|  | ||||
| func (c *serviceClient) Call(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { | ||||
| 	out := new(Response) | ||||
| 	err := c.cc.Invoke(ctx, "/proxy.Service/Call", in, out, opts...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| func (c *serviceClient) Stream(ctx context.Context, opts ...grpc.CallOption) (Service_StreamClient, error) { | ||||
| 	stream, err := c.cc.NewStream(ctx, &_Service_serviceDesc.Streams[0], "/proxy.Service/Stream", opts...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	x := &serviceStreamClient{stream} | ||||
| 	return x, nil | ||||
| } | ||||
|  | ||||
| type Service_StreamClient interface { | ||||
| 	Send(*Request) error | ||||
| 	Recv() (*Response, error) | ||||
| 	grpc.ClientStream | ||||
| } | ||||
|  | ||||
| type serviceStreamClient struct { | ||||
| 	grpc.ClientStream | ||||
| } | ||||
|  | ||||
| func (x *serviceStreamClient) Send(m *Request) error { | ||||
| 	return x.ClientStream.SendMsg(m) | ||||
| } | ||||
|  | ||||
| func (x *serviceStreamClient) Recv() (*Response, error) { | ||||
| 	m := new(Response) | ||||
| 	if err := x.ClientStream.RecvMsg(m); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return m, nil | ||||
| } | ||||
|  | ||||
| func (c *serviceClient) Publish(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Empty, error) { | ||||
| 	out := new(Empty) | ||||
| 	err := c.cc.Invoke(ctx, "/proxy.Service/Publish", in, out, opts...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| // ServiceServer is the server API for Service service. | ||||
| type ServiceServer interface { | ||||
| 	Call(context.Context, *Request) (*Response, error) | ||||
| 	Stream(Service_StreamServer) error | ||||
| 	Publish(context.Context, *Message) (*Empty, error) | ||||
| } | ||||
|  | ||||
| func RegisterServiceServer(s *grpc.Server, srv ServiceServer) { | ||||
| 	s.RegisterService(&_Service_serviceDesc, srv) | ||||
| } | ||||
|  | ||||
| func _Service_Call_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | ||||
| 	in := new(Request) | ||||
| 	if err := dec(in); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if interceptor == nil { | ||||
| 		return srv.(ServiceServer).Call(ctx, in) | ||||
| 	} | ||||
| 	info := &grpc.UnaryServerInfo{ | ||||
| 		Server:     srv, | ||||
| 		FullMethod: "/proxy.Service/Call", | ||||
| 	} | ||||
| 	handler := func(ctx context.Context, req interface{}) (interface{}, error) { | ||||
| 		return srv.(ServiceServer).Call(ctx, req.(*Request)) | ||||
| 	} | ||||
| 	return interceptor(ctx, in, info, handler) | ||||
| } | ||||
|  | ||||
| func _Service_Stream_Handler(srv interface{}, stream grpc.ServerStream) error { | ||||
| 	return srv.(ServiceServer).Stream(&serviceStreamServer{stream}) | ||||
| } | ||||
|  | ||||
| type Service_StreamServer interface { | ||||
| 	Send(*Response) error | ||||
| 	Recv() (*Request, error) | ||||
| 	grpc.ServerStream | ||||
| } | ||||
|  | ||||
| type serviceStreamServer struct { | ||||
| 	grpc.ServerStream | ||||
| } | ||||
|  | ||||
| func (x *serviceStreamServer) Send(m *Response) error { | ||||
| 	return x.ServerStream.SendMsg(m) | ||||
| } | ||||
|  | ||||
| func (x *serviceStreamServer) Recv() (*Request, error) { | ||||
| 	m := new(Request) | ||||
| 	if err := x.ServerStream.RecvMsg(m); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return m, nil | ||||
| } | ||||
|  | ||||
| func _Service_Publish_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | ||||
| 	in := new(Message) | ||||
| 	if err := dec(in); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if interceptor == nil { | ||||
| 		return srv.(ServiceServer).Publish(ctx, in) | ||||
| 	} | ||||
| 	info := &grpc.UnaryServerInfo{ | ||||
| 		Server:     srv, | ||||
| 		FullMethod: "/proxy.Service/Publish", | ||||
| 	} | ||||
| 	handler := func(ctx context.Context, req interface{}) (interface{}, error) { | ||||
| 		return srv.(ServiceServer).Publish(ctx, req.(*Message)) | ||||
| 	} | ||||
| 	return interceptor(ctx, in, info, handler) | ||||
| } | ||||
|  | ||||
| var _Service_serviceDesc = grpc.ServiceDesc{ | ||||
| 	ServiceName: "proxy.Service", | ||||
| 	HandlerType: (*ServiceServer)(nil), | ||||
| 	Methods: []grpc.MethodDesc{ | ||||
| 		{ | ||||
| 			MethodName: "Call", | ||||
| 			Handler:    _Service_Call_Handler, | ||||
| 		}, | ||||
| 		{ | ||||
| 			MethodName: "Publish", | ||||
| 			Handler:    _Service_Publish_Handler, | ||||
| 		}, | ||||
| 	}, | ||||
| 	Streams: []grpc.StreamDesc{ | ||||
| 		{ | ||||
| 			StreamName:    "Stream", | ||||
| 			Handler:       _Service_Stream_Handler, | ||||
| 			ServerStreams: true, | ||||
| 			ClientStreams: true, | ||||
| 		}, | ||||
| 	}, | ||||
| 	Metadata: "github.com/micro/go-proxy/proto/proxy.proto", | ||||
| } | ||||
							
								
								
									
										18
									
								
								proxy/proto/proxy.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								proxy/proto/proxy.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| syntax = "proto3"; | ||||
|  | ||||
| package proxy; | ||||
|  | ||||
| service Service { | ||||
| 	rpc Call(Request) returns (Response) {}; | ||||
| 	rpc Stream(stream Request) returns (stream Response) {}; | ||||
| 	rpc Publish(Message) returns (Empty) {}; | ||||
| 	rpc Subscribe(Message) returns (stream Message) {}; | ||||
| } | ||||
|  | ||||
| message Request {} | ||||
|  | ||||
| message Response {} | ||||
|  | ||||
| message Message {} | ||||
|  | ||||
| message Empty {} | ||||
							
								
								
									
										235
									
								
								proxy/router/grpc/grpc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								proxy/router/grpc/grpc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,235 @@ | ||||
| // Package grpc transparently forwards the grpc protocol using a go-micro client. | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"io" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/micro/go-micro" | ||||
| 	"github.com/micro/go-micro/client" | ||||
| 	"github.com/micro/go-micro/codec" | ||||
| 	"github.com/micro/go-micro/codec/bytes" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| 	"github.com/micro/go-micro/service/grpc" | ||||
| ) | ||||
|  | ||||
| // Router will transparently proxy requests to the backend. | ||||
| // If no backend is specified it will call a service using the client. | ||||
| // If the service matches the Name it will use the server.DefaultRouter. | ||||
| type Router struct { | ||||
| 	// Name of the local service. In the event it's to be left alone | ||||
| 	Name string | ||||
|  | ||||
| 	// Backend is a single backend to route to | ||||
| 	// If backend is of the form address:port it will call the address. | ||||
| 	// Otherwise it will use it as the service name to call. | ||||
| 	Backend string | ||||
|  | ||||
| 	// Endpoint specified the fixed endpoint to call. | ||||
| 	// In the event you proxy to a fixed backend this lets you | ||||
| 	// call a single endpoint | ||||
| 	Endpoint string | ||||
|  | ||||
| 	// The client to use for outbound requests | ||||
| 	Client client.Client | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	// The default name of this local service | ||||
| 	DefaultName = "go.micro.proxy" | ||||
| 	// The default router | ||||
| 	DefaultRouter = &Router{} | ||||
| ) | ||||
|  | ||||
| // read client request and write to server | ||||
| func readLoop(r server.Request, s client.Stream) error { | ||||
| 	// request to backend server | ||||
| 	req := s.Request() | ||||
|  | ||||
| 	for { | ||||
| 		// get data from client | ||||
| 		//  no need to decode it | ||||
| 		body, err := r.Read() | ||||
| 		if err == io.EOF { | ||||
| 			return nil | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		// get the header from client | ||||
| 		hdr := r.Header() | ||||
| 		msg := &codec.Message{ | ||||
| 			Type:   codec.Request, | ||||
| 			Header: hdr, | ||||
| 			Body:   body, | ||||
| 		} | ||||
| 		// write the raw request | ||||
| 		err = req.Codec().Write(msg, nil) | ||||
| 		if err == io.EOF { | ||||
| 			return nil | ||||
| 		} else if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ServeRequest honours the server.Router interface | ||||
| func (p *Router) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error { | ||||
| 	// set the default name e.g local proxy | ||||
| 	if p.Name == "" { | ||||
| 		p.Name = DefaultName | ||||
| 	} | ||||
|  | ||||
| 	// set default client | ||||
| 	if p.Client == nil { | ||||
| 		p.Client = client.DefaultClient | ||||
| 	} | ||||
|  | ||||
| 	// check service route | ||||
| 	if req.Service() == p.Name { | ||||
| 		// use the default router | ||||
| 		return server.DefaultRouter.ServeRequest(ctx, req, rsp) | ||||
| 	} | ||||
|  | ||||
| 	opts := []client.CallOption{} | ||||
|  | ||||
| 	// service name | ||||
| 	service := req.Service() | ||||
| 	endpoint := req.Endpoint() | ||||
|  | ||||
| 	// call a specific backend | ||||
| 	if len(p.Backend) > 0 { | ||||
| 		// address:port | ||||
| 		if parts := strings.Split(p.Backend, ":"); len(parts) > 0 { | ||||
| 			opts = append(opts, client.WithAddress(p.Backend)) | ||||
| 			// use as service name | ||||
| 		} else { | ||||
| 			service = p.Backend | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// call a specific endpoint | ||||
| 	if len(p.Endpoint) > 0 { | ||||
| 		endpoint = p.Endpoint | ||||
| 	} | ||||
|  | ||||
| 	// read initial request | ||||
| 	body, err := req.Read() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// create new request with raw bytes body | ||||
| 	creq := p.Client.NewRequest(service, endpoint, &bytes.Frame{body}, client.WithContentType(req.ContentType())) | ||||
|  | ||||
| 	// create new stream | ||||
| 	stream, err := p.Client.Stream(ctx, creq, opts...) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer stream.Close() | ||||
|  | ||||
| 	// create client request read loop | ||||
| 	go readLoop(req, stream) | ||||
|  | ||||
| 	// get raw response | ||||
| 	resp := stream.Response() | ||||
|  | ||||
| 	// create server response write loop | ||||
| 	for { | ||||
| 		// read backend response body | ||||
| 		body, err := resp.Read() | ||||
| 		if err == io.EOF { | ||||
| 			return nil | ||||
| 		} else if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		// read backend response header | ||||
| 		hdr := resp.Header() | ||||
|  | ||||
| 		// write raw response header to client | ||||
| 		rsp.WriteHeader(hdr) | ||||
|  | ||||
| 		// write raw response body to client | ||||
| 		err = rsp.Write(body) | ||||
| 		if err == io.EOF { | ||||
| 			return nil | ||||
| 		} else if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // NewSingleHostRouter returns a router which sends requests to a single backend | ||||
| // | ||||
| // It is used by setting it in a new micro service to act as a proxy for a backend. | ||||
| // | ||||
| // Usage: | ||||
| // | ||||
| // Create a new router to the http backend | ||||
| // | ||||
| // 	r := NewSingleHostRouter("localhost:10001") | ||||
| // | ||||
| // 	// Create your new service | ||||
| // 	service := micro.NewService( | ||||
| // 		micro.Name("greeter"), | ||||
| //		// Set the router | ||||
| //		http.WithRouter(r), | ||||
| // 	) | ||||
| // | ||||
| // 	// Run the service | ||||
| // 	service.Run() | ||||
| func NewSingleHostRouter(url string) *Router { | ||||
| 	return &Router{ | ||||
| 		Backend: url, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewService returns a new proxy. It acts as a micro service proxy. | ||||
| // Any request on the transport is routed to via the client to a service. | ||||
| // In the event a backend is specified then it routes to that backend. | ||||
| // The name of the backend can be a local address:port or a service name. | ||||
| // | ||||
| // Usage: | ||||
| // | ||||
| //	New micro proxy routes via micro client to any service | ||||
| // | ||||
| // 	proxy := NewService() | ||||
| // | ||||
| //	OR with address:port routes to local service | ||||
| // | ||||
| // 	service := NewService( | ||||
| //		// Sets the default http endpoint | ||||
| //		proxy.WithBackend("localhost:10001"), | ||||
| //	 ) | ||||
| // | ||||
| // 	OR with service name routes to a fixed backend service | ||||
| // | ||||
| // 	service := NewService( | ||||
| //		// Sets the backend service | ||||
| //		proxy.WithBackend("greeter"), | ||||
| //	 ) | ||||
| // | ||||
| func NewService(opts ...micro.Option) micro.Service { | ||||
| 	router := DefaultRouter | ||||
| 	name := DefaultName | ||||
|  | ||||
| 	// prepend router to opts | ||||
| 	opts = append([]micro.Option{ | ||||
| 		micro.Name(name), | ||||
| 		WithRouter(router), | ||||
| 	}, opts...) | ||||
|  | ||||
| 	// create the new service | ||||
| 	service := grpc.NewService(opts...) | ||||
|  | ||||
| 	// set router name | ||||
| 	router.Name = service.Server().Options().Name | ||||
|  | ||||
| 	return service | ||||
| } | ||||
							
								
								
									
										32
									
								
								proxy/router/grpc/options.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								proxy/router/grpc/options.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"github.com/micro/go-micro" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| // WithBackend provides an option to set the proxy backend url | ||||
| func WithBackend(url string) micro.Option { | ||||
| 	return func(o *micro.Options) { | ||||
| 		// get the router | ||||
| 		r := o.Server.Options().Router | ||||
|  | ||||
| 		// not set | ||||
| 		if r == nil { | ||||
| 			r = DefaultRouter | ||||
| 			o.Server.Init(server.WithRouter(r)) | ||||
| 		} | ||||
|  | ||||
| 		// check its a proxy router | ||||
| 		if proxyRouter, ok := r.(*Router); ok { | ||||
| 			proxyRouter.Backend = url | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithRouter provides an option to set the proxy router | ||||
| func WithRouter(r server.Router) micro.Option { | ||||
| 	return func(o *micro.Options) { | ||||
| 		o.Server.Init(server.WithRouter(r)) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										177
									
								
								proxy/router/http/http.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								proxy/router/http/http.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | ||||
| // Package http provides a micro rpc to http proxy | ||||
| package http | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
|  | ||||
| 	"github.com/micro/go-micro" | ||||
| 	"github.com/micro/go-micro/errors" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| // Router will proxy rpc requests as http POST requests. It is a server.Router | ||||
| type Router struct { | ||||
| 	// The http backend to call | ||||
| 	Backend string | ||||
|  | ||||
| 	// first request | ||||
| 	first bool | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	// The default backend | ||||
| 	DefaultBackend = "http://localhost:9090" | ||||
| 	// The default router | ||||
| 	DefaultRouter = &Router{} | ||||
| ) | ||||
|  | ||||
| func getMethod(hdr map[string]string) string { | ||||
| 	switch hdr["Micro-Method"] { | ||||
| 	case "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH": | ||||
| 		return hdr["Micro-Method"] | ||||
| 	default: | ||||
| 		return "POST" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func getEndpoint(hdr map[string]string) string { | ||||
| 	ep := hdr["Micro-Endpoint"] | ||||
| 	if len(ep) > 0 && ep[0] == '/' { | ||||
| 		return ep | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // ServeRequest honours the server.Router interface | ||||
| func (p *Router) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error { | ||||
| 	if p.Backend == "" { | ||||
| 		p.Backend = DefaultBackend | ||||
| 	} | ||||
|  | ||||
| 	for { | ||||
| 		// get data | ||||
| 		body, err := req.Read() | ||||
| 		if err == io.EOF { | ||||
| 			return nil | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		// get the header | ||||
| 		hdr := req.Header() | ||||
|  | ||||
| 		// get method | ||||
| 		method := getMethod(hdr) | ||||
|  | ||||
| 		// get endpoint | ||||
| 		endpoint := getEndpoint(hdr) | ||||
|  | ||||
| 		// set the endpoint | ||||
| 		if len(endpoint) == 0 { | ||||
| 			endpoint = p.Backend | ||||
| 		} else { | ||||
| 			// add endpoint to backend | ||||
| 			u, err := url.Parse(p.Backend) | ||||
| 			if err != nil { | ||||
| 				return errors.InternalServerError(req.Service(), err.Error()) | ||||
| 			} | ||||
| 			u.Path = path.Join(u.Path, endpoint) | ||||
| 			endpoint = u.String() | ||||
| 		} | ||||
|  | ||||
| 		// send to backend | ||||
| 		hreq, err := http.NewRequest(method, endpoint, bytes.NewReader(body)) | ||||
| 		if err != nil { | ||||
| 			return errors.InternalServerError(req.Service(), err.Error()) | ||||
| 		} | ||||
|  | ||||
| 		// set the headers | ||||
| 		for k, v := range hdr { | ||||
| 			hreq.Header.Set(k, v) | ||||
| 		} | ||||
|  | ||||
| 		// make the call | ||||
| 		hrsp, err := http.DefaultClient.Do(hreq) | ||||
| 		if err != nil { | ||||
| 			return errors.InternalServerError(req.Service(), err.Error()) | ||||
| 		} | ||||
|  | ||||
| 		// read body | ||||
| 		b, err := ioutil.ReadAll(hrsp.Body) | ||||
| 		hrsp.Body.Close() | ||||
| 		if err != nil { | ||||
| 			return errors.InternalServerError(req.Service(), err.Error()) | ||||
| 		} | ||||
|  | ||||
| 		// set response headers | ||||
| 		hdr = map[string]string{} | ||||
| 		for k, _ := range hrsp.Header { | ||||
| 			hdr[k] = hrsp.Header.Get(k) | ||||
| 		} | ||||
| 		// write the header | ||||
| 		rsp.WriteHeader(hdr) | ||||
| 		// write the body | ||||
| 		err = rsp.Write(b) | ||||
| 		if err == io.EOF { | ||||
| 			return nil | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return errors.InternalServerError(req.Service(), err.Error()) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // NewSingleHostRouter returns a router which sends requests to a single http backend | ||||
| // | ||||
| // It is used by setting it in a new micro service to act as a proxy for a http backend. | ||||
| // | ||||
| // Usage: | ||||
| // | ||||
| // Create a new router to the http backend | ||||
| // | ||||
| // 	r := NewSingleHostRouter("http://localhost:10001") | ||||
| // | ||||
| // 	// Create your new service | ||||
| // 	service := micro.NewService( | ||||
| // 		micro.Name("greeter"), | ||||
| //		// Set the router | ||||
| //		http.WithRouter(r), | ||||
| // 	) | ||||
| // | ||||
| // 	// Run the service | ||||
| // 	service.Run() | ||||
| func NewSingleHostRouter(url string) *Router { | ||||
| 	return &Router{ | ||||
| 		Backend: url, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewService returns a new http proxy. It acts as a micro service proxy. | ||||
| // Any request on the transport is routed to a fixed http backend. | ||||
| // | ||||
| // Usage: | ||||
| // | ||||
| // 	service := NewService( | ||||
| //		micro.Name("greeter"), | ||||
| //		// Sets the default http endpoint | ||||
| //		http.WithBackend("http://localhost:10001"), | ||||
| //	 ) | ||||
| // | ||||
| func NewService(opts ...micro.Option) micro.Service { | ||||
| 	// prepend router to opts | ||||
| 	opts = append([]micro.Option{ | ||||
| 		WithRouter(DefaultRouter), | ||||
| 	}, opts...) | ||||
|  | ||||
| 	// create the new service | ||||
| 	return micro.NewService(opts...) | ||||
| } | ||||
							
								
								
									
										122
									
								
								proxy/router/http/http_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								proxy/router/http/http_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| package http | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/micro/go-micro" | ||||
| 	"github.com/micro/go-micro/client" | ||||
| 	"github.com/micro/go-micro/registry/memory" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| type testHandler struct{} | ||||
|  | ||||
| func (t *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	w.Write([]byte(`{"hello": "world"}`)) | ||||
| } | ||||
|  | ||||
| func TestHTTPRouter(t *testing.T) { | ||||
| 	c, err := net.Listen("tcp", "localhost:0") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
| 	addr := c.Addr().String() | ||||
|  | ||||
| 	url := fmt.Sprintf("http://%s", addr) | ||||
|  | ||||
| 	testCases := []struct { | ||||
| 		// http endpoint to call e.g /foo/bar | ||||
| 		httpEp string | ||||
| 		// rpc endpoint called e.g Foo.Bar | ||||
| 		rpcEp string | ||||
| 		// should be an error | ||||
| 		err bool | ||||
| 	}{ | ||||
| 		{"/", "Foo.Bar", false}, | ||||
| 		{"/", "Foo.Baz", false}, | ||||
| 		{"/helloworld", "Hello.World", true}, | ||||
| 	} | ||||
|  | ||||
| 	// handler | ||||
| 	http.Handle("/", new(testHandler)) | ||||
|  | ||||
| 	// new proxy | ||||
| 	p := NewSingleHostRouter(url) | ||||
|  | ||||
| 	ctx, cancel := context.WithCancel(context.Background()) | ||||
| 	defer cancel() | ||||
|  | ||||
| 	var wg sync.WaitGroup | ||||
| 	wg.Add(1) | ||||
|  | ||||
| 	// new micro service | ||||
| 	service := micro.NewService( | ||||
| 		micro.Context(ctx), | ||||
| 		micro.Name("foobar"), | ||||
| 		micro.Registry(memory.NewRegistry()), | ||||
| 		micro.AfterStart(func() error { | ||||
| 			wg.Done() | ||||
| 			return nil | ||||
| 		}), | ||||
| 	) | ||||
|  | ||||
| 	// set router | ||||
| 	service.Server().Init( | ||||
| 		server.WithRouter(p), | ||||
| 	) | ||||
|  | ||||
| 	// run service | ||||
| 	// server | ||||
| 	go http.Serve(c, nil) | ||||
| 	go service.Run() | ||||
|  | ||||
| 	// wait till service is started | ||||
| 	wg.Wait() | ||||
|  | ||||
| 	for _, test := range testCases { | ||||
| 		req := service.Client().NewRequest("foobar", test.rpcEp, map[string]string{"foo": "bar"}, client.WithContentType("application/json")) | ||||
| 		var rsp map[string]string | ||||
| 		err := service.Client().Call(ctx, req, &rsp) | ||||
| 		if err != nil && test.err == false { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 		if v := rsp["hello"]; v != "world" { | ||||
| 			t.Fatalf("Expected hello world got %s from %s", v, test.rpcEp) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestHTTPRouterOptions(t *testing.T) { | ||||
| 	// test endpoint | ||||
| 	service := NewService( | ||||
| 		WithBackend("http://foo.bar"), | ||||
| 	) | ||||
|  | ||||
| 	r := service.Server().Options().Router | ||||
| 	httpRouter, ok := r.(*Router) | ||||
| 	if !ok { | ||||
| 		t.Fatal("Expected http router to be installed") | ||||
| 	} | ||||
| 	if httpRouter.Backend != "http://foo.bar" { | ||||
| 		t.Fatalf("Expected endpoint http://foo.bar got %v", httpRouter.Backend) | ||||
| 	} | ||||
|  | ||||
| 	// test router | ||||
| 	service = NewService( | ||||
| 		WithRouter(&Router{Backend: "http://foo2.bar"}), | ||||
| 	) | ||||
| 	r = service.Server().Options().Router | ||||
| 	httpRouter, ok = r.(*Router) | ||||
| 	if !ok { | ||||
| 		t.Fatal("Expected http router to be installed") | ||||
| 	} | ||||
| 	if httpRouter.Backend != "http://foo2.bar" { | ||||
| 		t.Fatalf("Expected endpoint http://foo2.bar got %v", httpRouter.Backend) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										32
									
								
								proxy/router/http/options.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								proxy/router/http/options.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| package http | ||||
|  | ||||
| import ( | ||||
| 	"github.com/micro/go-micro" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| // WithBackend provides an option to set the http backend url | ||||
| func WithBackend(url string) micro.Option { | ||||
| 	return func(o *micro.Options) { | ||||
| 		// get the router | ||||
| 		r := o.Server.Options().Router | ||||
|  | ||||
| 		// not set | ||||
| 		if r == nil { | ||||
| 			r = DefaultRouter | ||||
| 			o.Server.Init(server.WithRouter(r)) | ||||
| 		} | ||||
|  | ||||
| 		// check its a http router | ||||
| 		if httpRouter, ok := r.(*Router); ok { | ||||
| 			httpRouter.Backend = url | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithRouter provides an option to set the http router | ||||
| func WithRouter(r server.Router) micro.Option { | ||||
| 	return func(o *micro.Options) { | ||||
| 		o.Server.Init(server.WithRouter(r)) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										234
									
								
								proxy/router/mucp/mucp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								proxy/router/mucp/mucp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,234 @@ | ||||
| // Package mucp transparently forwards the incoming request using a go-micro client. | ||||
| package mucp | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"io" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/micro/go-micro" | ||||
| 	"github.com/micro/go-micro/client" | ||||
| 	"github.com/micro/go-micro/codec" | ||||
| 	"github.com/micro/go-micro/codec/bytes" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| // Router will transparently proxy requests to the backend. | ||||
| // If no backend is specified it will call a service using the client. | ||||
| // If the service matches the Name it will use the server.DefaultRouter. | ||||
| type Router struct { | ||||
| 	// Name of the local service. In the event it's to be left alone | ||||
| 	Name string | ||||
|  | ||||
| 	// Backend is a single backend to route to | ||||
| 	// If backend is of the form address:port it will call the address. | ||||
| 	// Otherwise it will use it as the service name to call. | ||||
| 	Backend string | ||||
|  | ||||
| 	// Endpoint specified the fixed endpoint to call. | ||||
| 	// In the event you proxy to a fixed backend this lets you | ||||
| 	// call a single endpoint | ||||
| 	Endpoint string | ||||
|  | ||||
| 	// The client to use for outbound requests | ||||
| 	Client client.Client | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	// The default name of this local service | ||||
| 	DefaultName = "go.micro.proxy" | ||||
| 	// The default router | ||||
| 	DefaultRouter = &Router{} | ||||
| ) | ||||
|  | ||||
| // read client request and write to server | ||||
| func readLoop(r server.Request, s client.Stream) error { | ||||
| 	// request to backend server | ||||
| 	req := s.Request() | ||||
|  | ||||
| 	for { | ||||
| 		// get data from client | ||||
| 		//  no need to decode it | ||||
| 		body, err := r.Read() | ||||
| 		if err == io.EOF { | ||||
| 			return nil | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		// get the header from client | ||||
| 		hdr := r.Header() | ||||
| 		msg := &codec.Message{ | ||||
| 			Type:   codec.Request, | ||||
| 			Header: hdr, | ||||
| 			Body:   body, | ||||
| 		} | ||||
| 		// write the raw request | ||||
| 		err = req.Codec().Write(msg, nil) | ||||
| 		if err == io.EOF { | ||||
| 			return nil | ||||
| 		} else if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ServeRequest honours the server.Router interface | ||||
| func (p *Router) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error { | ||||
| 	// set the default name e.g local proxy | ||||
| 	if p.Name == "" { | ||||
| 		p.Name = DefaultName | ||||
| 	} | ||||
|  | ||||
| 	// set default client | ||||
| 	if p.Client == nil { | ||||
| 		p.Client = client.DefaultClient | ||||
| 	} | ||||
|  | ||||
| 	// check service route | ||||
| 	if req.Service() == p.Name { | ||||
| 		// use the default router | ||||
| 		return server.DefaultRouter.ServeRequest(ctx, req, rsp) | ||||
| 	} | ||||
|  | ||||
| 	opts := []client.CallOption{} | ||||
|  | ||||
| 	// service name | ||||
| 	service := req.Service() | ||||
| 	endpoint := req.Endpoint() | ||||
|  | ||||
| 	// call a specific backend | ||||
| 	if len(p.Backend) > 0 { | ||||
| 		// address:port | ||||
| 		if parts := strings.Split(p.Backend, ":"); len(parts) > 0 { | ||||
| 			opts = append(opts, client.WithAddress(p.Backend)) | ||||
| 			// use as service name | ||||
| 		} else { | ||||
| 			service = p.Backend | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// call a specific endpoint | ||||
| 	if len(p.Endpoint) > 0 { | ||||
| 		endpoint = p.Endpoint | ||||
| 	} | ||||
|  | ||||
| 	// read initial request | ||||
| 	body, err := req.Read() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// create new request with raw bytes body | ||||
| 	creq := p.Client.NewRequest(service, endpoint, &bytes.Frame{body}, client.WithContentType(req.ContentType())) | ||||
|  | ||||
| 	// create new stream | ||||
| 	stream, err := p.Client.Stream(ctx, creq, opts...) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer stream.Close() | ||||
|  | ||||
| 	// create client request read loop | ||||
| 	go readLoop(req, stream) | ||||
|  | ||||
| 	// get raw response | ||||
| 	resp := stream.Response() | ||||
|  | ||||
| 	// create server response write loop | ||||
| 	for { | ||||
| 		// read backend response body | ||||
| 		body, err := resp.Read() | ||||
| 		if err == io.EOF { | ||||
| 			return nil | ||||
| 		} else if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		// read backend response header | ||||
| 		hdr := resp.Header() | ||||
|  | ||||
| 		// write raw response header to client | ||||
| 		rsp.WriteHeader(hdr) | ||||
|  | ||||
| 		// write raw response body to client | ||||
| 		err = rsp.Write(body) | ||||
| 		if err == io.EOF { | ||||
| 			return nil | ||||
| 		} else if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // NewSingleHostRouter returns a router which sends requests to a single backend | ||||
| // | ||||
| // It is used by setting it in a new micro service to act as a proxy for a backend. | ||||
| // | ||||
| // Usage: | ||||
| // | ||||
| // Create a new router to the http backend | ||||
| // | ||||
| // 	r := NewSingleHostRouter("localhost:10001") | ||||
| // | ||||
| // 	// Create your new service | ||||
| // 	service := micro.NewService( | ||||
| // 		micro.Name("greeter"), | ||||
| //		// Set the router | ||||
| //		http.WithRouter(r), | ||||
| // 	) | ||||
| // | ||||
| // 	// Run the service | ||||
| // 	service.Run() | ||||
| func NewSingleHostRouter(url string) *Router { | ||||
| 	return &Router{ | ||||
| 		Backend: url, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewService returns a new proxy. It acts as a micro service proxy. | ||||
| // Any request on the transport is routed to via the client to a service. | ||||
| // In the event a backend is specified then it routes to that backend. | ||||
| // The name of the backend can be a local address:port or a service name. | ||||
| // | ||||
| // Usage: | ||||
| // | ||||
| //	New micro proxy routes via micro client to any service | ||||
| // | ||||
| // 	proxy := NewService() | ||||
| // | ||||
| //	OR with address:port routes to local service | ||||
| // | ||||
| // 	service := NewService( | ||||
| //		// Sets the default http endpoint | ||||
| //		proxy.WithBackend("localhost:10001"), | ||||
| //	 ) | ||||
| // | ||||
| // 	OR with service name routes to a fixed backend service | ||||
| // | ||||
| // 	service := NewService( | ||||
| //		// Sets the backend service | ||||
| //		proxy.WithBackend("greeter"), | ||||
| //	 ) | ||||
| // | ||||
| func NewService(opts ...micro.Option) micro.Service { | ||||
| 	router := DefaultRouter | ||||
| 	name := DefaultName | ||||
|  | ||||
| 	// prepend router to opts | ||||
| 	opts = append([]micro.Option{ | ||||
| 		micro.Name(name), | ||||
| 		WithRouter(router), | ||||
| 	}, opts...) | ||||
|  | ||||
| 	// create the new service | ||||
| 	service := micro.NewService(opts...) | ||||
|  | ||||
| 	// set router name | ||||
| 	router.Name = service.Server().Options().Name | ||||
|  | ||||
| 	return service | ||||
| } | ||||
							
								
								
									
										32
									
								
								proxy/router/mucp/options.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								proxy/router/mucp/options.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| package mucp | ||||
|  | ||||
| import ( | ||||
| 	"github.com/micro/go-micro" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| // WithBackend provides an option to set the proxy backend url | ||||
| func WithBackend(url string) micro.Option { | ||||
| 	return func(o *micro.Options) { | ||||
| 		// get the router | ||||
| 		r := o.Server.Options().Router | ||||
|  | ||||
| 		// not set | ||||
| 		if r == nil { | ||||
| 			r = DefaultRouter | ||||
| 			o.Server.Init(server.WithRouter(r)) | ||||
| 		} | ||||
|  | ||||
| 		// check its a proxy router | ||||
| 		if proxyRouter, ok := r.(*Router); ok { | ||||
| 			proxyRouter.Backend = url | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithRouter provides an option to set the proxy router | ||||
| func WithRouter(r server.Router) micro.Option { | ||||
| 	return func(o *micro.Options) { | ||||
| 		o.Server.Init(server.WithRouter(r)) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										30
									
								
								server/grpc/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								server/grpc/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| # GRPC Server | ||||
|  | ||||
| The grpc server is a [micro.Server](https://godoc.org/github.com/micro/go-micro/server#Server) compatible server. | ||||
|  | ||||
| ## Overview | ||||
|  | ||||
| The server makes use of the [google.golang.org/grpc](google.golang.org/grpc) framework for the underlying server  | ||||
| but continues to use micro handler signatures and protoc-gen-micro generated code. | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
| Specify the server to your micro service | ||||
|  | ||||
| ```go | ||||
| import ( | ||||
|         "github.com/micro/go-micro" | ||||
|         "github.com/micro/go-plugins/server/grpc" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
|         service := micro.NewService( | ||||
|                 // This needs to be first as it replaces the underlying server | ||||
|                 // which causes any configuration set before it | ||||
|                 // to be discarded | ||||
|                 micro.Server(grpc.NewServer()), | ||||
|                 micro.Name("greeter"), | ||||
|         ) | ||||
| } | ||||
| ``` | ||||
| **NOTE**: Setting the gRPC server and/or client causes the underlying the server/client to be replaced which causes any previous configuration set on that server/client to be discarded. It is therefore recommended to set gRPC server/client before any other configuration | ||||
							
								
								
									
										14
									
								
								server/grpc/buffer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								server/grpc/buffer.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| ) | ||||
|  | ||||
| type buffer struct { | ||||
| 	*bytes.Buffer | ||||
| } | ||||
|  | ||||
| func (b *buffer) Close() error { | ||||
| 	b.Buffer.Reset() | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										82
									
								
								server/grpc/codec.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								server/grpc/codec.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/golang/protobuf/proto" | ||||
| 	"github.com/micro/go-micro/codec" | ||||
| 	"github.com/micro/go-micro/codec/jsonrpc" | ||||
| 	"github.com/micro/go-micro/codec/protorpc" | ||||
| 	"google.golang.org/grpc/encoding" | ||||
| ) | ||||
|  | ||||
| type jsonCodec struct{} | ||||
| type bytesCodec struct{} | ||||
| type protoCodec struct{} | ||||
|  | ||||
| var ( | ||||
| 	defaultGRPCCodecs = map[string]encoding.Codec{ | ||||
| 		"application/json":         jsonCodec{}, | ||||
| 		"application/proto":        protoCodec{}, | ||||
| 		"application/protobuf":     protoCodec{}, | ||||
| 		"application/octet-stream": protoCodec{}, | ||||
| 		"application/grpc":         protoCodec{}, | ||||
| 		"application/grpc+json":    jsonCodec{}, | ||||
| 		"application/grpc+proto":   protoCodec{}, | ||||
| 		"application/grpc+bytes":   bytesCodec{}, | ||||
| 	} | ||||
|  | ||||
| 	defaultRPCCodecs = map[string]codec.NewCodec{ | ||||
| 		"application/json":         jsonrpc.NewCodec, | ||||
| 		"application/json-rpc":     jsonrpc.NewCodec, | ||||
| 		"application/protobuf":     protorpc.NewCodec, | ||||
| 		"application/proto-rpc":    protorpc.NewCodec, | ||||
| 		"application/octet-stream": protorpc.NewCodec, | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func (protoCodec) Marshal(v interface{}) ([]byte, error) { | ||||
| 	return proto.Marshal(v.(proto.Message)) | ||||
| } | ||||
|  | ||||
| func (protoCodec) Unmarshal(data []byte, v interface{}) error { | ||||
| 	return proto.Unmarshal(data, v.(proto.Message)) | ||||
| } | ||||
|  | ||||
| func (protoCodec) Name() string { | ||||
| 	return "proto" | ||||
| } | ||||
|  | ||||
| func (jsonCodec) Marshal(v interface{}) ([]byte, error) { | ||||
| 	return json.Marshal(v) | ||||
| } | ||||
|  | ||||
| func (jsonCodec) Unmarshal(data []byte, v interface{}) error { | ||||
| 	return json.Unmarshal(data, v) | ||||
| } | ||||
|  | ||||
| func (jsonCodec) Name() string { | ||||
| 	return "json" | ||||
| } | ||||
|  | ||||
| func (bytesCodec) Marshal(v interface{}) ([]byte, error) { | ||||
| 	b, ok := v.(*[]byte) | ||||
| 	if !ok { | ||||
| 		return nil, fmt.Errorf("failed to marshal: %v is not type of *[]byte", v) | ||||
| 	} | ||||
| 	return *b, nil | ||||
| } | ||||
|  | ||||
| func (bytesCodec) Unmarshal(data []byte, v interface{}) error { | ||||
| 	b, ok := v.(*[]byte) | ||||
| 	if !ok { | ||||
| 		return fmt.Errorf("failed to unmarshal: %v is not type of *[]byte", v) | ||||
| 	} | ||||
| 	*b = data | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (bytesCodec) Name() string { | ||||
| 	return "bytes" | ||||
| } | ||||
							
								
								
									
										15
									
								
								server/grpc/debug.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								server/grpc/debug.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"github.com/micro/go-micro/server" | ||||
| 	"github.com/micro/go-micro/server/debug" | ||||
| ) | ||||
|  | ||||
| // We use this to wrap any debug handlers so we preserve the signature Debug.{Method} | ||||
| type Debug struct { | ||||
| 	debug.DebugHandler | ||||
| } | ||||
|  | ||||
| func registerDebugHandler(s server.Server) { | ||||
| 	s.Handle(s.NewHandler(&Debug{s.Options().DebugHandler}, server.InternalHandler(true))) | ||||
| } | ||||
							
								
								
									
										42
									
								
								server/grpc/error.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								server/grpc/error.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/micro/go-micro/errors" | ||||
| 	"google.golang.org/grpc/codes" | ||||
| ) | ||||
|  | ||||
| func microError(err *errors.Error) codes.Code { | ||||
| 	switch err { | ||||
| 	case nil: | ||||
| 		return codes.OK | ||||
| 	} | ||||
|  | ||||
| 	switch err.Code { | ||||
| 	case http.StatusOK: | ||||
| 		return codes.OK | ||||
| 	case http.StatusBadRequest: | ||||
| 		return codes.InvalidArgument | ||||
| 	case http.StatusRequestTimeout: | ||||
| 		return codes.DeadlineExceeded | ||||
| 	case http.StatusNotFound: | ||||
| 		return codes.NotFound | ||||
| 	case http.StatusConflict: | ||||
| 		return codes.AlreadyExists | ||||
| 	case http.StatusForbidden: | ||||
| 		return codes.PermissionDenied | ||||
| 	case http.StatusUnauthorized: | ||||
| 		return codes.Unauthenticated | ||||
| 	case http.StatusPreconditionFailed: | ||||
| 		return codes.FailedPrecondition | ||||
| 	case http.StatusNotImplemented: | ||||
| 		return codes.Unimplemented | ||||
| 	case http.StatusInternalServerError: | ||||
| 		return codes.Internal | ||||
| 	case http.StatusServiceUnavailable: | ||||
| 		return codes.Unavailable | ||||
| 	} | ||||
|  | ||||
| 	return codes.Unknown | ||||
| } | ||||
							
								
								
									
										120
									
								
								server/grpc/extractor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								server/grpc/extractor.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| ) | ||||
|  | ||||
| func extractValue(v reflect.Type, d int) *registry.Value { | ||||
| 	if d == 3 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if v == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if v.Kind() == reflect.Ptr { | ||||
| 		v = v.Elem() | ||||
| 	} | ||||
|  | ||||
| 	arg := ®istry.Value{ | ||||
| 		Name: v.Name(), | ||||
| 		Type: v.Name(), | ||||
| 	} | ||||
|  | ||||
| 	switch v.Kind() { | ||||
| 	case reflect.Struct: | ||||
| 		for i := 0; i < v.NumField(); i++ { | ||||
| 			f := v.Field(i) | ||||
| 			val := extractValue(f.Type, d+1) | ||||
| 			if val == nil { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			// if we can find a json tag use it | ||||
| 			if tags := f.Tag.Get("json"); len(tags) > 0 { | ||||
| 				parts := strings.Split(tags, ",") | ||||
| 				if parts[0] == "-" || parts[0] == "omitempty" { | ||||
| 					continue | ||||
| 				} | ||||
| 				val.Name = parts[0] | ||||
| 			} | ||||
|  | ||||
| 			// if there's no name default it | ||||
| 			if len(val.Name) == 0 { | ||||
| 				val.Name = v.Field(i).Name | ||||
| 			} | ||||
|  | ||||
| 			arg.Values = append(arg.Values, val) | ||||
| 		} | ||||
| 	case reflect.Slice: | ||||
| 		p := v.Elem() | ||||
| 		if p.Kind() == reflect.Ptr { | ||||
| 			p = p.Elem() | ||||
| 		} | ||||
| 		arg.Type = "[]" + p.Name() | ||||
| 		val := extractValue(v.Elem(), d+1) | ||||
| 		if val != nil { | ||||
| 			arg.Values = append(arg.Values, val) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return arg | ||||
| } | ||||
|  | ||||
| func extractEndpoint(method reflect.Method) *registry.Endpoint { | ||||
| 	if method.PkgPath != "" { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	var rspType, reqType reflect.Type | ||||
| 	var stream bool | ||||
| 	mt := method.Type | ||||
|  | ||||
| 	switch mt.NumIn() { | ||||
| 	case 3: | ||||
| 		reqType = mt.In(1) | ||||
| 		rspType = mt.In(2) | ||||
| 	case 4: | ||||
| 		reqType = mt.In(2) | ||||
| 		rspType = mt.In(3) | ||||
| 	default: | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// are we dealing with a stream? | ||||
| 	switch rspType.Kind() { | ||||
| 	case reflect.Func, reflect.Interface: | ||||
| 		stream = true | ||||
| 	} | ||||
|  | ||||
| 	request := extractValue(reqType, 0) | ||||
| 	response := extractValue(rspType, 0) | ||||
|  | ||||
| 	return ®istry.Endpoint{ | ||||
| 		Name:     method.Name, | ||||
| 		Request:  request, | ||||
| 		Response: response, | ||||
| 		Metadata: map[string]string{ | ||||
| 			"stream": fmt.Sprintf("%v", stream), | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func extractSubValue(typ reflect.Type) *registry.Value { | ||||
| 	var reqType reflect.Type | ||||
| 	switch typ.NumIn() { | ||||
| 	case 1: | ||||
| 		reqType = typ.In(0) | ||||
| 	case 2: | ||||
| 		reqType = typ.In(1) | ||||
| 	case 3: | ||||
| 		reqType = typ.In(2) | ||||
| 	default: | ||||
| 		return nil | ||||
| 	} | ||||
| 	return extractValue(reqType, 0) | ||||
| } | ||||
							
								
								
									
										65
									
								
								server/grpc/extractor_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								server/grpc/extractor_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| ) | ||||
|  | ||||
| type testHandler struct{} | ||||
|  | ||||
| type testRequest struct{} | ||||
|  | ||||
| type testResponse struct{} | ||||
|  | ||||
| func (t *testHandler) Test(ctx context.Context, req *testRequest, rsp *testResponse) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func TestExtractEndpoint(t *testing.T) { | ||||
| 	handler := &testHandler{} | ||||
| 	typ := reflect.TypeOf(handler) | ||||
|  | ||||
| 	var endpoints []*registry.Endpoint | ||||
|  | ||||
| 	for m := 0; m < typ.NumMethod(); m++ { | ||||
| 		if e := extractEndpoint(typ.Method(m)); e != nil { | ||||
| 			endpoints = append(endpoints, e) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if i := len(endpoints); i != 1 { | ||||
| 		t.Errorf("Expected 1 endpoint, have %d", i) | ||||
| 	} | ||||
|  | ||||
| 	if endpoints[0].Name != "Test" { | ||||
| 		t.Errorf("Expected handler Test, got %s", endpoints[0].Name) | ||||
| 	} | ||||
|  | ||||
| 	if endpoints[0].Request == nil { | ||||
| 		t.Error("Expected non nil request") | ||||
| 	} | ||||
|  | ||||
| 	if endpoints[0].Response == nil { | ||||
| 		t.Error("Expected non nil request") | ||||
| 	} | ||||
|  | ||||
| 	if endpoints[0].Request.Name != "testRequest" { | ||||
| 		t.Errorf("Expected testRequest got %s", endpoints[0].Request.Name) | ||||
| 	} | ||||
|  | ||||
| 	if endpoints[0].Response.Name != "testResponse" { | ||||
| 		t.Errorf("Expected testResponse got %s", endpoints[0].Response.Name) | ||||
| 	} | ||||
|  | ||||
| 	if endpoints[0].Request.Type != "testRequest" { | ||||
| 		t.Errorf("Expected testRequest type got %s", endpoints[0].Request.Type) | ||||
| 	} | ||||
|  | ||||
| 	if endpoints[0].Response.Type != "testResponse" { | ||||
| 		t.Errorf("Expected testResponse type got %s", endpoints[0].Response.Type) | ||||
| 	} | ||||
|  | ||||
| } | ||||
							
								
								
									
										731
									
								
								server/grpc/grpc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										731
									
								
								server/grpc/grpc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,731 @@ | ||||
| // Package grpc provides a grpc server | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"reflect" | ||||
| 	"sort" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/micro/go-micro/broker" | ||||
| 	"github.com/micro/go-micro/cmd" | ||||
| 	"github.com/micro/go-micro/codec" | ||||
| 	"github.com/micro/go-micro/errors" | ||||
| 	meta "github.com/micro/go-micro/metadata" | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| 	"github.com/micro/go-micro/util/addr" | ||||
| 	mgrpc "github.com/micro/go-micro/util/grpc" | ||||
| 	"github.com/micro/go-micro/util/log" | ||||
|  | ||||
| 	"google.golang.org/grpc" | ||||
| 	"google.golang.org/grpc/codes" | ||||
| 	"google.golang.org/grpc/credentials" | ||||
| 	"google.golang.org/grpc/encoding" | ||||
| 	"google.golang.org/grpc/metadata" | ||||
| 	"google.golang.org/grpc/status" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// DefaultMaxMsgSize define maximum message size that server can send | ||||
| 	// or receive.  Default value is 4MB. | ||||
| 	DefaultMaxMsgSize = 1024 * 1024 * 4 | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	defaultContentType = "application/grpc" | ||||
| ) | ||||
|  | ||||
| type grpcServer struct { | ||||
| 	rpc  *rServer | ||||
| 	srv  *grpc.Server | ||||
| 	exit chan chan error | ||||
| 	wg   *sync.WaitGroup | ||||
|  | ||||
| 	sync.RWMutex | ||||
| 	opts        server.Options | ||||
| 	handlers    map[string]server.Handler | ||||
| 	subscribers map[*subscriber][]broker.Subscriber | ||||
| 	// used for first registration | ||||
| 	registered bool | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	encoding.RegisterCodec(jsonCodec{}) | ||||
| 	encoding.RegisterCodec(bytesCodec{}) | ||||
|  | ||||
| 	cmd.DefaultServers["grpc"] = NewServer | ||||
| } | ||||
|  | ||||
| func newGRPCServer(opts ...server.Option) server.Server { | ||||
| 	options := newOptions(opts...) | ||||
|  | ||||
| 	// create a grpc server | ||||
| 	srv := &grpcServer{ | ||||
| 		opts: options, | ||||
| 		rpc: &rServer{ | ||||
| 			serviceMap: make(map[string]*service), | ||||
| 		}, | ||||
| 		handlers:    make(map[string]server.Handler), | ||||
| 		subscribers: make(map[*subscriber][]broker.Subscriber), | ||||
| 		exit:        make(chan chan error), | ||||
| 		wg:          wait(options.Context), | ||||
| 	} | ||||
|  | ||||
| 	// configure the grpc server | ||||
| 	srv.configure() | ||||
|  | ||||
| 	return srv | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) configure(opts ...server.Option) { | ||||
| 	// Don't reprocess where there's no config | ||||
| 	if len(opts) == 0 && g.srv != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, o := range opts { | ||||
| 		o(&g.opts) | ||||
| 	} | ||||
|  | ||||
| 	maxMsgSize := g.getMaxMsgSize() | ||||
|  | ||||
| 	gopts := []grpc.ServerOption{ | ||||
| 		grpc.MaxRecvMsgSize(maxMsgSize), | ||||
| 		grpc.MaxSendMsgSize(maxMsgSize), | ||||
| 		grpc.UnknownServiceHandler(g.handler), | ||||
| 	} | ||||
|  | ||||
| 	if creds := g.getCredentials(); creds != nil { | ||||
| 		gopts = append(gopts, grpc.Creds(creds)) | ||||
| 	} | ||||
|  | ||||
| 	if opts := g.getGrpcOptions(); opts != nil { | ||||
| 		gopts = append(gopts, opts...) | ||||
| 	} | ||||
|  | ||||
| 	g.srv = grpc.NewServer(gopts...) | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) getMaxMsgSize() int { | ||||
| 	if g.opts.Context == nil { | ||||
| 		return DefaultMaxMsgSize | ||||
| 	} | ||||
| 	s, ok := g.opts.Context.Value(maxMsgSizeKey{}).(int) | ||||
| 	if !ok { | ||||
| 		return DefaultMaxMsgSize | ||||
| 	} | ||||
| 	return s | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) getCredentials() credentials.TransportCredentials { | ||||
| 	if g.opts.Context != nil { | ||||
| 		if v := g.opts.Context.Value(tlsAuth{}); v != nil { | ||||
| 			tls := v.(*tls.Config) | ||||
| 			return credentials.NewTLS(tls) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) getGrpcOptions() []grpc.ServerOption { | ||||
| 	if g.opts.Context == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	v := g.opts.Context.Value(grpcOptions{}) | ||||
|  | ||||
| 	if v == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	opts, ok := v.([]grpc.ServerOption) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	return opts | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) handler(srv interface{}, stream grpc.ServerStream) error { | ||||
| 	if g.wg != nil { | ||||
| 		g.wg.Add(1) | ||||
| 		defer g.wg.Done() | ||||
| 	} | ||||
|  | ||||
| 	fullMethod, ok := grpc.MethodFromServerStream(stream) | ||||
| 	if !ok { | ||||
| 		return grpc.Errorf(codes.Internal, "method does not exist in context") | ||||
| 	} | ||||
|  | ||||
| 	serviceName, methodName, err := mgrpc.ServiceMethod(fullMethod) | ||||
| 	if err != nil { | ||||
| 		return status.New(codes.InvalidArgument, err.Error()).Err() | ||||
| 	} | ||||
|  | ||||
| 	g.rpc.mu.Lock() | ||||
| 	service := g.rpc.serviceMap[serviceName] | ||||
| 	g.rpc.mu.Unlock() | ||||
|  | ||||
| 	if service == nil { | ||||
| 		return status.New(codes.Unimplemented, fmt.Sprintf("unknown service %v", service)).Err() | ||||
| 	} | ||||
|  | ||||
| 	mtype := service.method[methodName] | ||||
| 	if mtype == nil { | ||||
| 		return status.New(codes.Unimplemented, fmt.Sprintf("unknown service %v", service)).Err() | ||||
| 	} | ||||
|  | ||||
| 	// get grpc metadata | ||||
| 	gmd, ok := metadata.FromIncomingContext(stream.Context()) | ||||
| 	if !ok { | ||||
| 		gmd = metadata.MD{} | ||||
| 	} | ||||
|  | ||||
| 	// copy the metadata to go-micro.metadata | ||||
| 	md := meta.Metadata{} | ||||
| 	for k, v := range gmd { | ||||
| 		md[k] = strings.Join(v, ", ") | ||||
| 	} | ||||
|  | ||||
| 	// timeout for server deadline | ||||
| 	to := md["timeout"] | ||||
|  | ||||
| 	// get content type | ||||
| 	ct := defaultContentType | ||||
| 	if ctype, ok := md["x-content-type"]; ok { | ||||
| 		ct = ctype | ||||
| 	} | ||||
|  | ||||
| 	delete(md, "x-content-type") | ||||
| 	delete(md, "timeout") | ||||
|  | ||||
| 	// create new context | ||||
| 	ctx := meta.NewContext(stream.Context(), md) | ||||
|  | ||||
| 	// set the timeout if we have it | ||||
| 	if len(to) > 0 { | ||||
| 		if n, err := strconv.ParseUint(to, 10, 64); err == nil { | ||||
| 			ctx, _ = context.WithTimeout(ctx, time.Duration(n)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// process unary | ||||
| 	if !mtype.stream { | ||||
| 		return g.processRequest(stream, service, mtype, ct, ctx) | ||||
| 	} | ||||
|  | ||||
| 	// process stream | ||||
| 	return g.processStream(stream, service, mtype, ct, ctx) | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) processRequest(stream grpc.ServerStream, service *service, mtype *methodType, ct string, ctx context.Context) error { | ||||
| 	for { | ||||
| 		var argv, replyv reflect.Value | ||||
|  | ||||
| 		// Decode the argument value. | ||||
| 		argIsValue := false // if true, need to indirect before calling. | ||||
| 		if mtype.ArgType.Kind() == reflect.Ptr { | ||||
| 			argv = reflect.New(mtype.ArgType.Elem()) | ||||
| 		} else { | ||||
| 			argv = reflect.New(mtype.ArgType) | ||||
| 			argIsValue = true | ||||
| 		} | ||||
|  | ||||
| 		// Unmarshal request | ||||
| 		if err := stream.RecvMsg(argv.Interface()); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		if argIsValue { | ||||
| 			argv = argv.Elem() | ||||
| 		} | ||||
|  | ||||
| 		// reply value | ||||
| 		replyv = reflect.New(mtype.ReplyType.Elem()) | ||||
|  | ||||
| 		function := mtype.method.Func | ||||
| 		var returnValues []reflect.Value | ||||
|  | ||||
| 		cc, err := g.newGRPCCodec(ct) | ||||
| 		if err != nil { | ||||
| 			return errors.InternalServerError("go.micro.server", err.Error()) | ||||
| 		} | ||||
| 		b, err := cc.Marshal(argv.Interface()) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		// create a client.Request | ||||
| 		r := &rpcRequest{ | ||||
| 			service:     g.opts.Name, | ||||
| 			contentType: ct, | ||||
| 			method:      fmt.Sprintf("%s.%s", service.name, mtype.method.Name), | ||||
| 			body:        b, | ||||
| 			payload:     argv.Interface(), | ||||
| 		} | ||||
|  | ||||
| 		// define the handler func | ||||
| 		fn := func(ctx context.Context, req server.Request, rsp interface{}) error { | ||||
| 			returnValues = function.Call([]reflect.Value{service.rcvr, mtype.prepareContext(ctx), reflect.ValueOf(argv.Interface()), reflect.ValueOf(rsp)}) | ||||
|  | ||||
| 			// The return value for the method is an error. | ||||
| 			if err := returnValues[0].Interface(); err != nil { | ||||
| 				return err.(error) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		// wrap the handler func | ||||
| 		for i := len(g.opts.HdlrWrappers); i > 0; i-- { | ||||
| 			fn = g.opts.HdlrWrappers[i-1](fn) | ||||
| 		} | ||||
|  | ||||
| 		statusCode := codes.OK | ||||
| 		statusDesc := "" | ||||
|  | ||||
| 		// execute the handler | ||||
| 		if appErr := fn(ctx, r, replyv.Interface()); appErr != nil { | ||||
| 			if err, ok := appErr.(*rpcError); ok { | ||||
| 				statusCode = err.code | ||||
| 				statusDesc = err.desc | ||||
| 			} else if err, ok := appErr.(*errors.Error); ok { | ||||
| 				statusCode = microError(err) | ||||
| 				statusDesc = appErr.Error() | ||||
| 			} else { | ||||
| 				statusCode = convertCode(appErr) | ||||
| 				statusDesc = appErr.Error() | ||||
| 			} | ||||
| 			return status.New(statusCode, statusDesc).Err() | ||||
| 		} | ||||
| 		if err := stream.SendMsg(replyv.Interface()); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return status.New(statusCode, statusDesc).Err() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) processStream(stream grpc.ServerStream, service *service, mtype *methodType, ct string, ctx context.Context) error { | ||||
| 	opts := g.opts | ||||
|  | ||||
| 	r := &rpcRequest{ | ||||
| 		service:     opts.Name, | ||||
| 		contentType: ct, | ||||
| 		method:      fmt.Sprintf("%s.%s", service.name, mtype.method.Name), | ||||
| 		stream:      true, | ||||
| 	} | ||||
|  | ||||
| 	ss := &rpcStream{ | ||||
| 		request: r, | ||||
| 		s:       stream, | ||||
| 	} | ||||
|  | ||||
| 	function := mtype.method.Func | ||||
| 	var returnValues []reflect.Value | ||||
|  | ||||
| 	// Invoke the method, providing a new value for the reply. | ||||
| 	fn := func(ctx context.Context, req server.Request, stream interface{}) error { | ||||
| 		returnValues = function.Call([]reflect.Value{service.rcvr, mtype.prepareContext(ctx), reflect.ValueOf(stream)}) | ||||
| 		if err := returnValues[0].Interface(); err != nil { | ||||
| 			return err.(error) | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	for i := len(opts.HdlrWrappers); i > 0; i-- { | ||||
| 		fn = opts.HdlrWrappers[i-1](fn) | ||||
| 	} | ||||
|  | ||||
| 	statusCode := codes.OK | ||||
| 	statusDesc := "" | ||||
|  | ||||
| 	appErr := fn(ctx, r, ss) | ||||
| 	if appErr != nil { | ||||
| 		if err, ok := appErr.(*rpcError); ok { | ||||
| 			statusCode = err.code | ||||
| 			statusDesc = err.desc | ||||
| 		} else if err, ok := appErr.(*errors.Error); ok { | ||||
| 			statusCode = microError(err) | ||||
| 			statusDesc = appErr.Error() | ||||
| 		} else { | ||||
| 			statusCode = convertCode(appErr) | ||||
| 			statusDesc = appErr.Error() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return status.New(statusCode, statusDesc).Err() | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) newGRPCCodec(contentType string) (encoding.Codec, error) { | ||||
| 	codecs := make(map[string]encoding.Codec) | ||||
| 	if g.opts.Context != nil { | ||||
| 		if v := g.opts.Context.Value(codecsKey{}); v != nil { | ||||
| 			codecs = v.(map[string]encoding.Codec) | ||||
| 		} | ||||
| 	} | ||||
| 	if c, ok := codecs[contentType]; ok { | ||||
| 		return c, nil | ||||
| 	} | ||||
| 	if c, ok := defaultGRPCCodecs[contentType]; ok { | ||||
| 		return c, nil | ||||
| 	} | ||||
| 	return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType) | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) newCodec(contentType string) (codec.NewCodec, error) { | ||||
| 	if cf, ok := g.opts.Codecs[contentType]; ok { | ||||
| 		return cf, nil | ||||
| 	} | ||||
| 	if cf, ok := defaultRPCCodecs[contentType]; ok { | ||||
| 		return cf, nil | ||||
| 	} | ||||
| 	return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType) | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) Options() server.Options { | ||||
| 	opts := g.opts | ||||
| 	return opts | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) Init(opts ...server.Option) error { | ||||
| 	g.configure(opts...) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) NewHandler(h interface{}, opts ...server.HandlerOption) server.Handler { | ||||
| 	return newRpcHandler(h, opts...) | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) Handle(h server.Handler) error { | ||||
| 	if err := g.rpc.register(h.Handler()); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	g.handlers[h.Name()] = h | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) NewSubscriber(topic string, sb interface{}, opts ...server.SubscriberOption) server.Subscriber { | ||||
| 	return newSubscriber(topic, sb, opts...) | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) Subscribe(sb server.Subscriber) error { | ||||
| 	sub, ok := sb.(*subscriber) | ||||
| 	if !ok { | ||||
| 		return fmt.Errorf("invalid subscriber: expected *subscriber") | ||||
| 	} | ||||
| 	if len(sub.handlers) == 0 { | ||||
| 		return fmt.Errorf("invalid subscriber: no handler functions") | ||||
| 	} | ||||
|  | ||||
| 	if err := validateSubscriber(sb); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	g.Lock() | ||||
|  | ||||
| 	_, ok = g.subscribers[sub] | ||||
| 	if ok { | ||||
| 		return fmt.Errorf("subscriber %v already exists", sub) | ||||
| 	} | ||||
| 	g.subscribers[sub] = nil | ||||
| 	g.Unlock() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) Register() error { | ||||
| 	// parse address for host, port | ||||
| 	config := g.opts | ||||
| 	var advt, host string | ||||
| 	var port int | ||||
|  | ||||
| 	// check the advertise address first | ||||
| 	// if it exists then use it, otherwise | ||||
| 	// use the address | ||||
| 	if len(config.Advertise) > 0 { | ||||
| 		advt = config.Advertise | ||||
| 	} else { | ||||
| 		advt = config.Address | ||||
| 	} | ||||
|  | ||||
| 	parts := strings.Split(advt, ":") | ||||
| 	if len(parts) > 1 { | ||||
| 		host = strings.Join(parts[:len(parts)-1], ":") | ||||
| 		port, _ = strconv.Atoi(parts[len(parts)-1]) | ||||
| 	} else { | ||||
| 		host = parts[0] | ||||
| 	} | ||||
|  | ||||
| 	addr, err := addr.Extract(host) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// register service | ||||
| 	node := ®istry.Node{ | ||||
| 		Id:       config.Name + "-" + config.Id, | ||||
| 		Address:  addr, | ||||
| 		Port:     port, | ||||
| 		Metadata: config.Metadata, | ||||
| 	} | ||||
|  | ||||
| 	node.Metadata["broker"] = config.Broker.String() | ||||
| 	node.Metadata["registry"] = config.Registry.String() | ||||
| 	node.Metadata["server"] = g.String() | ||||
| 	node.Metadata["transport"] = g.String() | ||||
| 	// node.Metadata["transport"] = config.Transport.String() | ||||
|  | ||||
| 	g.RLock() | ||||
| 	// Maps are ordered randomly, sort the keys for consistency | ||||
| 	var handlerList []string | ||||
| 	for n, e := range g.handlers { | ||||
| 		// Only advertise non internal handlers | ||||
| 		if !e.Options().Internal { | ||||
| 			handlerList = append(handlerList, n) | ||||
| 		} | ||||
| 	} | ||||
| 	sort.Strings(handlerList) | ||||
|  | ||||
| 	var subscriberList []*subscriber | ||||
| 	for e := range g.subscribers { | ||||
| 		// Only advertise non internal subscribers | ||||
| 		if !e.Options().Internal { | ||||
| 			subscriberList = append(subscriberList, e) | ||||
| 		} | ||||
| 	} | ||||
| 	sort.Slice(subscriberList, func(i, j int) bool { | ||||
| 		return subscriberList[i].topic > subscriberList[j].topic | ||||
| 	}) | ||||
|  | ||||
| 	var endpoints []*registry.Endpoint | ||||
| 	for _, n := range handlerList { | ||||
| 		endpoints = append(endpoints, g.handlers[n].Endpoints()...) | ||||
| 	} | ||||
| 	for _, e := range subscriberList { | ||||
| 		endpoints = append(endpoints, e.Endpoints()...) | ||||
| 	} | ||||
| 	g.RUnlock() | ||||
|  | ||||
| 	service := ®istry.Service{ | ||||
| 		Name:      config.Name, | ||||
| 		Version:   config.Version, | ||||
| 		Nodes:     []*registry.Node{node}, | ||||
| 		Endpoints: endpoints, | ||||
| 	} | ||||
|  | ||||
| 	g.Lock() | ||||
| 	registered := g.registered | ||||
| 	g.Unlock() | ||||
|  | ||||
| 	if !registered { | ||||
| 		log.Logf("Registering node: %s", node.Id) | ||||
| 	} | ||||
|  | ||||
| 	// create registry options | ||||
| 	rOpts := []registry.RegisterOption{registry.RegisterTTL(config.RegisterTTL)} | ||||
|  | ||||
| 	if err := config.Registry.Register(service, rOpts...); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// already registered? don't need to register subscribers | ||||
| 	if registered { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	g.Lock() | ||||
| 	defer g.Unlock() | ||||
|  | ||||
| 	g.registered = true | ||||
|  | ||||
| 	for sb, _ := range g.subscribers { | ||||
| 		handler := g.createSubHandler(sb, g.opts) | ||||
| 		var opts []broker.SubscribeOption | ||||
| 		if queue := sb.Options().Queue; len(queue) > 0 { | ||||
| 			opts = append(opts, broker.Queue(queue)) | ||||
| 		} | ||||
|  | ||||
| 		if !sb.Options().AutoAck { | ||||
| 			opts = append(opts, broker.DisableAutoAck()) | ||||
| 		} | ||||
|  | ||||
| 		sub, err := config.Broker.Subscribe(sb.Topic(), handler, opts...) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		g.subscribers[sb] = []broker.Subscriber{sub} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) Deregister() error { | ||||
| 	config := g.opts | ||||
| 	var advt, host string | ||||
| 	var port int | ||||
|  | ||||
| 	// check the advertise address first | ||||
| 	// if it exists then use it, otherwise | ||||
| 	// use the address | ||||
| 	if len(config.Advertise) > 0 { | ||||
| 		advt = config.Advertise | ||||
| 	} else { | ||||
| 		advt = config.Address | ||||
| 	} | ||||
|  | ||||
| 	parts := strings.Split(advt, ":") | ||||
| 	if len(parts) > 1 { | ||||
| 		host = strings.Join(parts[:len(parts)-1], ":") | ||||
| 		port, _ = strconv.Atoi(parts[len(parts)-1]) | ||||
| 	} else { | ||||
| 		host = parts[0] | ||||
| 	} | ||||
|  | ||||
| 	addr, err := addr.Extract(host) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	node := ®istry.Node{ | ||||
| 		Id:      config.Name + "-" + config.Id, | ||||
| 		Address: addr, | ||||
| 		Port:    port, | ||||
| 	} | ||||
|  | ||||
| 	service := ®istry.Service{ | ||||
| 		Name:    config.Name, | ||||
| 		Version: config.Version, | ||||
| 		Nodes:   []*registry.Node{node}, | ||||
| 	} | ||||
|  | ||||
| 	log.Logf("Deregistering node: %s", node.Id) | ||||
| 	if err := config.Registry.Deregister(service); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	g.Lock() | ||||
|  | ||||
| 	if !g.registered { | ||||
| 		g.Unlock() | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	g.registered = false | ||||
|  | ||||
| 	for sb, subs := range g.subscribers { | ||||
| 		for _, sub := range subs { | ||||
| 			log.Logf("Unsubscribing from topic: %s", sub.Topic()) | ||||
| 			sub.Unsubscribe() | ||||
| 		} | ||||
| 		g.subscribers[sb] = nil | ||||
| 	} | ||||
|  | ||||
| 	g.Unlock() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) Start() error { | ||||
| 	registerDebugHandler(g) | ||||
| 	config := g.opts | ||||
|  | ||||
| 	// micro: config.Transport.Listen(config.Address) | ||||
| 	ts, err := net.Listen("tcp", config.Address) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	log.Logf("Server [grpc] Listening on %s", ts.Addr().String()) | ||||
| 	g.Lock() | ||||
| 	g.opts.Address = ts.Addr().String() | ||||
| 	g.Unlock() | ||||
|  | ||||
| 	// connect to the broker | ||||
| 	if err := config.Broker.Connect(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	log.Logf("Broker [%s] Listening on %s", config.Broker.String(), config.Broker.Address()) | ||||
|  | ||||
| 	// announce self to the world | ||||
| 	if err := g.Register(); err != nil { | ||||
| 		log.Log("Server register error: ", err) | ||||
| 	} | ||||
|  | ||||
| 	// micro: go ts.Accept(s.accept) | ||||
| 	go func() { | ||||
| 		if err := g.srv.Serve(ts); err != nil { | ||||
| 			log.Log("gRPC Server start error: ", err) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	go func() { | ||||
| 		t := new(time.Ticker) | ||||
|  | ||||
| 		// only process if it exists | ||||
| 		if g.opts.RegisterInterval > time.Duration(0) { | ||||
| 			// new ticker | ||||
| 			t = time.NewTicker(g.opts.RegisterInterval) | ||||
| 		} | ||||
|  | ||||
| 		// return error chan | ||||
| 		var ch chan error | ||||
|  | ||||
| 	Loop: | ||||
| 		for { | ||||
| 			select { | ||||
| 			// register self on interval | ||||
| 			case <-t.C: | ||||
| 				if err := g.Register(); err != nil { | ||||
| 					log.Log("Server register error: ", err) | ||||
| 				} | ||||
| 			// wait for exit | ||||
| 			case ch = <-g.exit: | ||||
| 				break Loop | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// deregister self | ||||
| 		if err := g.Deregister(); err != nil { | ||||
| 			log.Log("Server deregister error: ", err) | ||||
| 		} | ||||
|  | ||||
| 		// wait for waitgroup | ||||
| 		if g.wg != nil { | ||||
| 			g.wg.Wait() | ||||
| 		} | ||||
|  | ||||
| 		// stop the grpc server | ||||
| 		g.srv.GracefulStop() | ||||
|  | ||||
| 		// close transport | ||||
| 		ch <- nil | ||||
|  | ||||
| 		// disconnect broker | ||||
| 		config.Broker.Disconnect() | ||||
| 	}() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) Stop() error { | ||||
| 	ch := make(chan error) | ||||
| 	g.exit <- ch | ||||
| 	return <-ch | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) String() string { | ||||
| 	return "grpc" | ||||
| } | ||||
|  | ||||
| func NewServer(opts ...server.Option) server.Server { | ||||
| 	return newGRPCServer(opts...) | ||||
| } | ||||
							
								
								
									
										66
									
								
								server/grpc/grpc_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								server/grpc/grpc_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/micro/go-micro/registry/memory" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| 	"google.golang.org/grpc" | ||||
|  | ||||
| 	pb "github.com/micro/examples/greeter/srv/proto/hello" | ||||
| ) | ||||
|  | ||||
| // server is used to implement helloworld.GreeterServer. | ||||
| type sayServer struct{} | ||||
|  | ||||
| // SayHello implements helloworld.GreeterServer | ||||
| func (s *sayServer) Hello(ctx context.Context, req *pb.Request, rsp *pb.Response) error { | ||||
| 	rsp.Msg = "Hello " + req.Name | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func TestGRPCServer(t *testing.T) { | ||||
| 	r := memory.NewRegistry() | ||||
| 	s := NewServer( | ||||
| 		server.Name("foo"), | ||||
| 		server.Registry(r), | ||||
| 	) | ||||
|  | ||||
| 	pb.RegisterSayHandler(s, &sayServer{}) | ||||
|  | ||||
| 	if err := s.Start(); err != nil { | ||||
| 		t.Fatalf("failed to start: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// check registration | ||||
| 	services, err := r.GetService("foo") | ||||
| 	if err != nil || len(services) == 0 { | ||||
| 		t.Fatalf("failed to get service: %v # %d", err, len(services)) | ||||
| 	} | ||||
|  | ||||
| 	defer func() { | ||||
| 		if err := s.Stop(); err != nil { | ||||
| 			t.Fatalf("failed to stop: %v", err) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	cc, err := grpc.Dial(s.Options().Address, grpc.WithInsecure()) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to dial server: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	testMethods := []string{"/helloworld.Say/Hello", "/greeter.helloworld.Say/Hello"} | ||||
|  | ||||
| 	for _, method := range testMethods { | ||||
| 		rsp := pb.Response{} | ||||
|  | ||||
| 		if err := cc.Invoke(context.Background(), method, &pb.Request{Name: "John"}, &rsp); err != nil { | ||||
| 			t.Fatalf("error calling server: %v", err) | ||||
| 		} | ||||
|  | ||||
| 		if rsp.Msg != "Hello John" { | ||||
| 			t.Fatalf("Got unexpected response %v", rsp.Msg) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										66
									
								
								server/grpc/handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								server/grpc/handler.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
|  | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| type rpcHandler struct { | ||||
| 	name      string | ||||
| 	handler   interface{} | ||||
| 	endpoints []*registry.Endpoint | ||||
| 	opts      server.HandlerOptions | ||||
| } | ||||
|  | ||||
| func newRpcHandler(handler interface{}, opts ...server.HandlerOption) server.Handler { | ||||
| 	options := server.HandlerOptions{ | ||||
| 		Metadata: make(map[string]map[string]string), | ||||
| 	} | ||||
|  | ||||
| 	for _, o := range opts { | ||||
| 		o(&options) | ||||
| 	} | ||||
|  | ||||
| 	typ := reflect.TypeOf(handler) | ||||
| 	hdlr := reflect.ValueOf(handler) | ||||
| 	name := reflect.Indirect(hdlr).Type().Name() | ||||
|  | ||||
| 	var endpoints []*registry.Endpoint | ||||
|  | ||||
| 	for m := 0; m < typ.NumMethod(); m++ { | ||||
| 		if e := extractEndpoint(typ.Method(m)); e != nil { | ||||
| 			e.Name = name + "." + e.Name | ||||
|  | ||||
| 			for k, v := range options.Metadata[e.Name] { | ||||
| 				e.Metadata[k] = v | ||||
| 			} | ||||
|  | ||||
| 			endpoints = append(endpoints, e) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &rpcHandler{ | ||||
| 		name:      name, | ||||
| 		handler:   handler, | ||||
| 		endpoints: endpoints, | ||||
| 		opts:      options, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (r *rpcHandler) Name() string { | ||||
| 	return r.name | ||||
| } | ||||
|  | ||||
| func (r *rpcHandler) Handler() interface{} { | ||||
| 	return r.handler | ||||
| } | ||||
|  | ||||
| func (r *rpcHandler) Endpoints() []*registry.Endpoint { | ||||
| 	return r.endpoints | ||||
| } | ||||
|  | ||||
| func (r *rpcHandler) Options() server.HandlerOptions { | ||||
| 	return r.opts | ||||
| } | ||||
							
								
								
									
										113
									
								
								server/grpc/options.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								server/grpc/options.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
|  | ||||
| 	"github.com/micro/go-micro/broker" | ||||
| 	"github.com/micro/go-micro/codec" | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| 	"github.com/micro/go-micro/server/debug" | ||||
| 	"github.com/micro/go-micro/transport" | ||||
| 	"google.golang.org/grpc" | ||||
| 	"google.golang.org/grpc/encoding" | ||||
| ) | ||||
|  | ||||
| type codecsKey struct{} | ||||
| type tlsAuth struct{} | ||||
| type maxMsgSizeKey struct{} | ||||
| type grpcOptions struct{} | ||||
|  | ||||
| // gRPC Codec to be used to encode/decode requests for a given content type | ||||
| func Codec(contentType string, c encoding.Codec) server.Option { | ||||
| 	return func(o *server.Options) { | ||||
| 		codecs := make(map[string]encoding.Codec) | ||||
| 		if o.Context == nil { | ||||
| 			o.Context = context.Background() | ||||
| 		} | ||||
| 		if v := o.Context.Value(codecsKey{}); v != nil { | ||||
| 			codecs = v.(map[string]encoding.Codec) | ||||
| 		} | ||||
| 		codecs[contentType] = c | ||||
| 		o.Context = context.WithValue(o.Context, codecsKey{}, codecs) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // AuthTLS should be used to setup a secure authentication using TLS | ||||
| func AuthTLS(t *tls.Config) server.Option { | ||||
| 	return func(o *server.Options) { | ||||
| 		if o.Context == nil { | ||||
| 			o.Context = context.Background() | ||||
| 		} | ||||
| 		o.Context = context.WithValue(o.Context, tlsAuth{}, t) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Options to be used to configure gRPC options | ||||
| func Options(opts ...grpc.ServerOption) server.Option { | ||||
| 	return func(o *server.Options) { | ||||
| 		if o.Context == nil { | ||||
| 			o.Context = context.Background() | ||||
| 		} | ||||
| 		o.Context = context.WithValue(o.Context, grpcOptions{}, opts) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // | ||||
| // MaxMsgSize set the maximum message in bytes the server can receive and | ||||
| // send.  Default maximum message size is 4 MB. | ||||
| // | ||||
| func MaxMsgSize(s int) server.Option { | ||||
| 	return func(o *server.Options) { | ||||
| 		if o.Context == nil { | ||||
| 			o.Context = context.Background() | ||||
| 		} | ||||
| 		o.Context = context.WithValue(o.Context, maxMsgSizeKey{}, s) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func newOptions(opt ...server.Option) server.Options { | ||||
| 	opts := server.Options{ | ||||
| 		Codecs:   make(map[string]codec.NewCodec), | ||||
| 		Metadata: map[string]string{}, | ||||
| 	} | ||||
|  | ||||
| 	for _, o := range opt { | ||||
| 		o(&opts) | ||||
| 	} | ||||
|  | ||||
| 	if opts.Broker == nil { | ||||
| 		opts.Broker = broker.DefaultBroker | ||||
| 	} | ||||
|  | ||||
| 	if opts.Registry == nil { | ||||
| 		opts.Registry = registry.DefaultRegistry | ||||
| 	} | ||||
|  | ||||
| 	if opts.Transport == nil { | ||||
| 		opts.Transport = transport.DefaultTransport | ||||
| 	} | ||||
|  | ||||
| 	if opts.DebugHandler == nil { | ||||
| 		opts.DebugHandler = debug.DefaultDebugHandler | ||||
| 	} | ||||
|  | ||||
| 	if len(opts.Address) == 0 { | ||||
| 		opts.Address = server.DefaultAddress | ||||
| 	} | ||||
|  | ||||
| 	if len(opts.Name) == 0 { | ||||
| 		opts.Name = server.DefaultName | ||||
| 	} | ||||
|  | ||||
| 	if len(opts.Id) == 0 { | ||||
| 		opts.Id = server.DefaultId | ||||
| 	} | ||||
|  | ||||
| 	if len(opts.Version) == 0 { | ||||
| 		opts.Version = server.DefaultVersion | ||||
| 	} | ||||
|  | ||||
| 	return opts | ||||
| } | ||||
							
								
								
									
										70
									
								
								server/grpc/request.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								server/grpc/request.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"github.com/micro/go-micro/codec" | ||||
| ) | ||||
|  | ||||
| type rpcRequest struct { | ||||
| 	service     string | ||||
| 	method      string | ||||
| 	contentType string | ||||
| 	codec       codec.Codec | ||||
| 	header      map[string]string | ||||
| 	body        []byte | ||||
| 	stream      bool | ||||
| 	payload     interface{} | ||||
| } | ||||
|  | ||||
| type rpcMessage struct { | ||||
| 	topic       string | ||||
| 	contentType string | ||||
| 	payload     interface{} | ||||
| } | ||||
|  | ||||
| func (r *rpcRequest) ContentType() string { | ||||
| 	return r.contentType | ||||
| } | ||||
|  | ||||
| func (r *rpcRequest) Service() string { | ||||
| 	return r.service | ||||
| } | ||||
|  | ||||
| func (r *rpcRequest) Method() string { | ||||
| 	return r.method | ||||
| } | ||||
|  | ||||
| func (r *rpcRequest) Endpoint() string { | ||||
| 	return r.method | ||||
| } | ||||
|  | ||||
| func (r *rpcRequest) Codec() codec.Reader { | ||||
| 	return r.codec | ||||
| } | ||||
|  | ||||
| func (r *rpcRequest) Header() map[string]string { | ||||
| 	return r.header | ||||
| } | ||||
|  | ||||
| func (r *rpcRequest) Read() ([]byte, error) { | ||||
| 	return r.body, nil | ||||
| } | ||||
|  | ||||
| func (r *rpcRequest) Stream() bool { | ||||
| 	return r.stream | ||||
| } | ||||
|  | ||||
| func (r *rpcRequest) Body() interface{} { | ||||
| 	return r.payload | ||||
| } | ||||
|  | ||||
| func (r *rpcMessage) ContentType() string { | ||||
| 	return r.contentType | ||||
| } | ||||
|  | ||||
| func (r *rpcMessage) Topic() string { | ||||
| 	return r.topic | ||||
| } | ||||
|  | ||||
| func (r *rpcMessage) Payload() interface{} { | ||||
| 	return r.payload | ||||
| } | ||||
							
								
								
									
										180
									
								
								server/grpc/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								server/grpc/server.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | ||||
| package grpc | ||||
|  | ||||
| // Copyright 2009 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
| // | ||||
| // Meh, we need to get rid of this shit | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"reflect" | ||||
| 	"sync" | ||||
| 	"unicode" | ||||
| 	"unicode/utf8" | ||||
|  | ||||
| 	"github.com/micro/go-micro/server" | ||||
| 	"github.com/micro/go-micro/util/log" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// Precompute the reflect type for error. Can't use error directly | ||||
| 	// because Typeof takes an empty interface value. This is annoying. | ||||
| 	typeOfError = reflect.TypeOf((*error)(nil)).Elem() | ||||
| ) | ||||
|  | ||||
| type methodType struct { | ||||
| 	method      reflect.Method | ||||
| 	ArgType     reflect.Type | ||||
| 	ReplyType   reflect.Type | ||||
| 	ContextType reflect.Type | ||||
| 	stream      bool | ||||
| } | ||||
|  | ||||
| type service struct { | ||||
| 	name   string                 // name of service | ||||
| 	rcvr   reflect.Value          // receiver of methods for the service | ||||
| 	typ    reflect.Type           // type of the receiver | ||||
| 	method map[string]*methodType // registered methods | ||||
| } | ||||
|  | ||||
| // server represents an RPC Server. | ||||
| type rServer struct { | ||||
| 	mu         sync.Mutex // protects the serviceMap | ||||
| 	serviceMap map[string]*service | ||||
| } | ||||
|  | ||||
| // Is this an exported - upper case - name? | ||||
| func isExported(name string) bool { | ||||
| 	rune, _ := utf8.DecodeRuneInString(name) | ||||
| 	return unicode.IsUpper(rune) | ||||
| } | ||||
|  | ||||
| // Is this type exported or a builtin? | ||||
| func isExportedOrBuiltinType(t reflect.Type) bool { | ||||
| 	for t.Kind() == reflect.Ptr { | ||||
| 		t = t.Elem() | ||||
| 	} | ||||
| 	// PkgPath will be non-empty even for an exported type, | ||||
| 	// so we need to check the type name as well. | ||||
| 	return isExported(t.Name()) || t.PkgPath() == "" | ||||
| } | ||||
|  | ||||
| // prepareEndpoint() returns a methodType for the provided method or nil | ||||
| // in case if the method was unsuitable. | ||||
| func prepareEndpoint(method reflect.Method) *methodType { | ||||
| 	mtype := method.Type | ||||
| 	mname := method.Name | ||||
| 	var replyType, argType, contextType reflect.Type | ||||
| 	var stream bool | ||||
|  | ||||
| 	// Endpoint() must be exported. | ||||
| 	if method.PkgPath != "" { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	switch mtype.NumIn() { | ||||
| 	case 3: | ||||
| 		// assuming streaming | ||||
| 		argType = mtype.In(2) | ||||
| 		contextType = mtype.In(1) | ||||
| 		stream = true | ||||
| 	case 4: | ||||
| 		// method that takes a context | ||||
| 		argType = mtype.In(2) | ||||
| 		replyType = mtype.In(3) | ||||
| 		contextType = mtype.In(1) | ||||
| 	default: | ||||
| 		log.Log("method", mname, "of", mtype, "has wrong number of ins:", mtype.NumIn()) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if stream { | ||||
| 		// check stream type | ||||
| 		streamType := reflect.TypeOf((*server.Stream)(nil)).Elem() | ||||
| 		if !argType.Implements(streamType) { | ||||
| 			log.Log(mname, "argument does not implement Streamer interface:", argType) | ||||
| 			return nil | ||||
| 		} | ||||
| 	} else { | ||||
| 		// if not stream check the replyType | ||||
|  | ||||
| 		// First arg need not be a pointer. | ||||
| 		if !isExportedOrBuiltinType(argType) { | ||||
| 			log.Log(mname, "argument type not exported:", argType) | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		if replyType.Kind() != reflect.Ptr { | ||||
| 			log.Log("method", mname, "reply type not a pointer:", replyType) | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		// Reply type must be exported. | ||||
| 		if !isExportedOrBuiltinType(replyType) { | ||||
| 			log.Log("method", mname, "reply type not exported:", replyType) | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Endpoint() needs one out. | ||||
| 	if mtype.NumOut() != 1 { | ||||
| 		log.Log("method", mname, "has wrong number of outs:", mtype.NumOut()) | ||||
| 		return nil | ||||
| 	} | ||||
| 	// The return type of the method must be error. | ||||
| 	if returnType := mtype.Out(0); returnType != typeOfError { | ||||
| 		log.Log("method", mname, "returns", returnType.String(), "not error") | ||||
| 		return nil | ||||
| 	} | ||||
| 	return &methodType{method: method, ArgType: argType, ReplyType: replyType, ContextType: contextType, stream: stream} | ||||
| } | ||||
|  | ||||
| func (server *rServer) register(rcvr interface{}) error { | ||||
| 	server.mu.Lock() | ||||
| 	defer server.mu.Unlock() | ||||
| 	if server.serviceMap == nil { | ||||
| 		server.serviceMap = make(map[string]*service) | ||||
| 	} | ||||
| 	s := new(service) | ||||
| 	s.typ = reflect.TypeOf(rcvr) | ||||
| 	s.rcvr = reflect.ValueOf(rcvr) | ||||
| 	sname := reflect.Indirect(s.rcvr).Type().Name() | ||||
| 	if sname == "" { | ||||
| 		log.Fatal("rpc: no service name for type", s.typ.String()) | ||||
| 	} | ||||
| 	if !isExported(sname) { | ||||
| 		s := "rpc Register: type " + sname + " is not exported" | ||||
| 		log.Log(s) | ||||
| 		return errors.New(s) | ||||
| 	} | ||||
| 	if _, present := server.serviceMap[sname]; present { | ||||
| 		return errors.New("rpc: service already defined: " + sname) | ||||
| 	} | ||||
| 	s.name = sname | ||||
| 	s.method = make(map[string]*methodType) | ||||
|  | ||||
| 	// Install the methods | ||||
| 	for m := 0; m < s.typ.NumMethod(); m++ { | ||||
| 		method := s.typ.Method(m) | ||||
| 		if mt := prepareEndpoint(method); mt != nil { | ||||
| 			s.method[method.Name] = mt | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(s.method) == 0 { | ||||
| 		s := "rpc Register: type " + sname + " has no exported methods of suitable type" | ||||
| 		log.Log(s) | ||||
| 		return errors.New(s) | ||||
| 	} | ||||
| 	server.serviceMap[s.name] = s | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *methodType) prepareContext(ctx context.Context) reflect.Value { | ||||
| 	if contextv := reflect.ValueOf(ctx); contextv.IsValid() { | ||||
| 		return contextv | ||||
| 	} | ||||
| 	return reflect.Zero(m.ContextType) | ||||
| } | ||||
							
								
								
									
										38
									
								
								server/grpc/stream.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								server/grpc/stream.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
|  | ||||
| 	"github.com/micro/go-micro/server" | ||||
| 	"google.golang.org/grpc" | ||||
| ) | ||||
|  | ||||
| // rpcStream implements a server side Stream. | ||||
| type rpcStream struct { | ||||
| 	s       grpc.ServerStream | ||||
| 	request server.Request | ||||
| } | ||||
|  | ||||
| func (r *rpcStream) Close() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (r *rpcStream) Error() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (r *rpcStream) Request() server.Request { | ||||
| 	return r.request | ||||
| } | ||||
|  | ||||
| func (r *rpcStream) Context() context.Context { | ||||
| 	return r.s.Context() | ||||
| } | ||||
|  | ||||
| func (r *rpcStream) Send(m interface{}) error { | ||||
| 	return r.s.SendMsg(m) | ||||
| } | ||||
|  | ||||
| func (r *rpcStream) Recv(m interface{}) error { | ||||
| 	return r.s.RecvMsg(m) | ||||
| } | ||||
							
								
								
									
										262
									
								
								server/grpc/subscriber.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								server/grpc/subscriber.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,262 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
|  | ||||
| 	"github.com/micro/go-micro/broker" | ||||
| 	"github.com/micro/go-micro/codec" | ||||
| 	"github.com/micro/go-micro/metadata" | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	subSig = "func(context.Context, interface{}) error" | ||||
| ) | ||||
|  | ||||
| type handler struct { | ||||
| 	method  reflect.Value | ||||
| 	reqType reflect.Type | ||||
| 	ctxType reflect.Type | ||||
| } | ||||
|  | ||||
| type subscriber struct { | ||||
| 	topic      string | ||||
| 	rcvr       reflect.Value | ||||
| 	typ        reflect.Type | ||||
| 	subscriber interface{} | ||||
| 	handlers   []*handler | ||||
| 	endpoints  []*registry.Endpoint | ||||
| 	opts       server.SubscriberOptions | ||||
| } | ||||
|  | ||||
| func newSubscriber(topic string, sub interface{}, opts ...server.SubscriberOption) server.Subscriber { | ||||
|  | ||||
| 	options := server.SubscriberOptions{ | ||||
| 		AutoAck: true, | ||||
| 	} | ||||
|  | ||||
| 	for _, o := range opts { | ||||
| 		o(&options) | ||||
| 	} | ||||
|  | ||||
| 	var endpoints []*registry.Endpoint | ||||
| 	var handlers []*handler | ||||
|  | ||||
| 	if typ := reflect.TypeOf(sub); typ.Kind() == reflect.Func { | ||||
| 		h := &handler{ | ||||
| 			method: reflect.ValueOf(sub), | ||||
| 		} | ||||
|  | ||||
| 		switch typ.NumIn() { | ||||
| 		case 1: | ||||
| 			h.reqType = typ.In(0) | ||||
| 		case 2: | ||||
| 			h.ctxType = typ.In(0) | ||||
| 			h.reqType = typ.In(1) | ||||
| 		} | ||||
|  | ||||
| 		handlers = append(handlers, h) | ||||
|  | ||||
| 		endpoints = append(endpoints, ®istry.Endpoint{ | ||||
| 			Name:    "Func", | ||||
| 			Request: extractSubValue(typ), | ||||
| 			Metadata: map[string]string{ | ||||
| 				"topic":      topic, | ||||
| 				"subscriber": "true", | ||||
| 			}, | ||||
| 		}) | ||||
| 	} else { | ||||
| 		hdlr := reflect.ValueOf(sub) | ||||
| 		name := reflect.Indirect(hdlr).Type().Name() | ||||
|  | ||||
| 		for m := 0; m < typ.NumMethod(); m++ { | ||||
| 			method := typ.Method(m) | ||||
| 			h := &handler{ | ||||
| 				method: method.Func, | ||||
| 			} | ||||
|  | ||||
| 			switch method.Type.NumIn() { | ||||
| 			case 2: | ||||
| 				h.reqType = method.Type.In(1) | ||||
| 			case 3: | ||||
| 				h.ctxType = method.Type.In(1) | ||||
| 				h.reqType = method.Type.In(2) | ||||
| 			} | ||||
|  | ||||
| 			handlers = append(handlers, h) | ||||
|  | ||||
| 			endpoints = append(endpoints, ®istry.Endpoint{ | ||||
| 				Name:    name + "." + method.Name, | ||||
| 				Request: extractSubValue(method.Type), | ||||
| 				Metadata: map[string]string{ | ||||
| 					"topic":      topic, | ||||
| 					"subscriber": "true", | ||||
| 				}, | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &subscriber{ | ||||
| 		rcvr:       reflect.ValueOf(sub), | ||||
| 		typ:        reflect.TypeOf(sub), | ||||
| 		topic:      topic, | ||||
| 		subscriber: sub, | ||||
| 		handlers:   handlers, | ||||
| 		endpoints:  endpoints, | ||||
| 		opts:       options, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func validateSubscriber(sub server.Subscriber) error { | ||||
| 	typ := reflect.TypeOf(sub.Subscriber()) | ||||
| 	var argType reflect.Type | ||||
|  | ||||
| 	if typ.Kind() == reflect.Func { | ||||
| 		name := "Func" | ||||
| 		switch typ.NumIn() { | ||||
| 		case 2: | ||||
| 			argType = typ.In(1) | ||||
| 		default: | ||||
| 			return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s", name, typ.NumIn(), subSig) | ||||
| 		} | ||||
| 		if !isExportedOrBuiltinType(argType) { | ||||
| 			return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType) | ||||
| 		} | ||||
| 		if typ.NumOut() != 1 { | ||||
| 			return fmt.Errorf("subscriber %v has wrong number of outs: %v require signature %s", | ||||
| 				name, typ.NumOut(), subSig) | ||||
| 		} | ||||
| 		if returnType := typ.Out(0); returnType != typeOfError { | ||||
| 			return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String()) | ||||
| 		} | ||||
| 	} else { | ||||
| 		hdlr := reflect.ValueOf(sub.Subscriber()) | ||||
| 		name := reflect.Indirect(hdlr).Type().Name() | ||||
|  | ||||
| 		for m := 0; m < typ.NumMethod(); m++ { | ||||
| 			method := typ.Method(m) | ||||
|  | ||||
| 			switch method.Type.NumIn() { | ||||
| 			case 3: | ||||
| 				argType = method.Type.In(2) | ||||
| 			default: | ||||
| 				return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s", | ||||
| 					name, method.Name, method.Type.NumIn(), subSig) | ||||
| 			} | ||||
|  | ||||
| 			if !isExportedOrBuiltinType(argType) { | ||||
| 				return fmt.Errorf("%v argument type not exported: %v", name, argType) | ||||
| 			} | ||||
| 			if method.Type.NumOut() != 1 { | ||||
| 				return fmt.Errorf( | ||||
| 					"subscriber %v.%v has wrong number of outs: %v require signature %s", | ||||
| 					name, method.Name, method.Type.NumOut(), subSig) | ||||
| 			} | ||||
| 			if returnType := method.Type.Out(0); returnType != typeOfError { | ||||
| 				return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String()) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (g *grpcServer) createSubHandler(sb *subscriber, opts server.Options) broker.Handler { | ||||
| 	return func(p broker.Publication) error { | ||||
| 		msg := p.Message() | ||||
| 		ct := msg.Header["Content-Type"] | ||||
| 		cf, err := g.newCodec(ct) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		hdr := make(map[string]string) | ||||
| 		for k, v := range msg.Header { | ||||
| 			hdr[k] = v | ||||
| 		} | ||||
| 		delete(hdr, "Content-Type") | ||||
| 		ctx := metadata.NewContext(context.Background(), hdr) | ||||
|  | ||||
| 		for i := 0; i < len(sb.handlers); i++ { | ||||
| 			handler := sb.handlers[i] | ||||
|  | ||||
| 			var isVal bool | ||||
| 			var req reflect.Value | ||||
|  | ||||
| 			if handler.reqType.Kind() == reflect.Ptr { | ||||
| 				req = reflect.New(handler.reqType.Elem()) | ||||
| 			} else { | ||||
| 				req = reflect.New(handler.reqType) | ||||
| 				isVal = true | ||||
| 			} | ||||
| 			if isVal { | ||||
| 				req = req.Elem() | ||||
| 			} | ||||
|  | ||||
| 			b := &buffer{bytes.NewBuffer(msg.Body)} | ||||
| 			co := cf(b) | ||||
| 			defer co.Close() | ||||
|  | ||||
| 			if err := co.ReadHeader(&codec.Message{}, codec.Publication); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			if err := co.ReadBody(req.Interface()); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			fn := func(ctx context.Context, msg server.Message) error { | ||||
| 				var vals []reflect.Value | ||||
| 				if sb.typ.Kind() != reflect.Func { | ||||
| 					vals = append(vals, sb.rcvr) | ||||
| 				} | ||||
| 				if handler.ctxType != nil { | ||||
| 					vals = append(vals, reflect.ValueOf(ctx)) | ||||
| 				} | ||||
|  | ||||
| 				vals = append(vals, reflect.ValueOf(msg.Payload())) | ||||
|  | ||||
| 				returnValues := handler.method.Call(vals) | ||||
| 				if err := returnValues[0].Interface(); err != nil { | ||||
| 					return err.(error) | ||||
| 				} | ||||
| 				return nil | ||||
| 			} | ||||
|  | ||||
| 			for i := len(opts.SubWrappers); i > 0; i-- { | ||||
| 				fn = opts.SubWrappers[i-1](fn) | ||||
| 			} | ||||
|  | ||||
| 			g.wg.Add(1) | ||||
| 			go func() { | ||||
| 				defer g.wg.Done() | ||||
| 				fn(ctx, &rpcMessage{ | ||||
| 					topic:       sb.topic, | ||||
| 					contentType: ct, | ||||
| 					payload:     req.Interface(), | ||||
| 				}) | ||||
| 			}() | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *subscriber) Topic() string { | ||||
| 	return s.topic | ||||
| } | ||||
|  | ||||
| func (s *subscriber) Subscriber() interface{} { | ||||
| 	return s.subscriber | ||||
| } | ||||
|  | ||||
| func (s *subscriber) Endpoints() []*registry.Endpoint { | ||||
| 	return s.endpoints | ||||
| } | ||||
|  | ||||
| func (s *subscriber) Options() server.SubscriberOptions { | ||||
| 	return s.opts | ||||
| } | ||||
							
								
								
									
										60
									
								
								server/grpc/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								server/grpc/util.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| package grpc | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"sync" | ||||
|  | ||||
| 	"google.golang.org/grpc/codes" | ||||
| ) | ||||
|  | ||||
| // rpcError defines the status from an RPC. | ||||
| type rpcError struct { | ||||
| 	code codes.Code | ||||
| 	desc string | ||||
| } | ||||
|  | ||||
| func (e *rpcError) Error() string { | ||||
| 	return fmt.Sprintf("rpc error: code = %d desc = %s", e.code, e.desc) | ||||
| } | ||||
|  | ||||
| // convertCode converts a standard Go error into its canonical code. Note that | ||||
| // this is only used to translate the error returned by the server applications. | ||||
| func convertCode(err error) codes.Code { | ||||
| 	switch err { | ||||
| 	case nil: | ||||
| 		return codes.OK | ||||
| 	case io.EOF: | ||||
| 		return codes.OutOfRange | ||||
| 	case io.ErrClosedPipe, io.ErrNoProgress, io.ErrShortBuffer, io.ErrShortWrite, io.ErrUnexpectedEOF: | ||||
| 		return codes.FailedPrecondition | ||||
| 	case os.ErrInvalid: | ||||
| 		return codes.InvalidArgument | ||||
| 	case context.Canceled: | ||||
| 		return codes.Canceled | ||||
| 	case context.DeadlineExceeded: | ||||
| 		return codes.DeadlineExceeded | ||||
| 	} | ||||
| 	switch { | ||||
| 	case os.IsExist(err): | ||||
| 		return codes.AlreadyExists | ||||
| 	case os.IsNotExist(err): | ||||
| 		return codes.NotFound | ||||
| 	case os.IsPermission(err): | ||||
| 		return codes.PermissionDenied | ||||
| 	} | ||||
| 	return codes.Unknown | ||||
| } | ||||
|  | ||||
| func wait(ctx context.Context) *sync.WaitGroup { | ||||
| 	if ctx == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	wg, ok := ctx.Value("wait").(*sync.WaitGroup) | ||||
| 	if !ok { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return wg | ||||
| } | ||||
							
								
								
									
										7
									
								
								service/grpc/.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								service/grpc/.travis.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| language: go | ||||
| go: | ||||
| - 1.10.x | ||||
| - 1.11.x | ||||
| notifications: | ||||
|   slack: | ||||
|     secure: aEvhLbhujaGaKSrOokiG3//PaVHTIrc3fBpoRbCRqfZpyq6WREoapJJhF+tIpWWOwaC9GmChbD6aHo/jMUgwKXVyPSaNjiEL87YzUUpL8B2zslNp1rgfTg/LrzthOx3Q1TYwpaAl3to0fuHUVFX4yMeC2vuThq7WSXgMMxFCtbc= | ||||
							
								
								
									
										36
									
								
								service/grpc/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								service/grpc/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| # Micro gRPC [](https://opensource.org/licenses/Apache-2.0) [](https://godoc.org/github.com/micro/go-micro/service/grpc) [](https://travis-ci.org/micro/go-micro/service/grpc) [](https://goreportcard.com/report/github.com/micro/go-micro/service/grpc) | ||||
|  | ||||
| A micro gRPC framework. A simplified experience for building gRPC services.  | ||||
|  | ||||
| ## Overview | ||||
|  | ||||
| **Go gRPC** makes use of [go-micro](https://github.com/micro/go-micro) plugins to create a simpler framework for gRPC development.  | ||||
| It interoperates with standard gRPC services seamlessly, including the [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway).  | ||||
| The go-grpc library uses the go-micro broker, client and server plugins which make use of  | ||||
| [github.com/grpc/grpc-go](https://github.com/grpc/grpc-go) internally.  | ||||
| This means we ignore the go-micro codec and transport but provide a native grpc experience. | ||||
|  | ||||
| <img src="https://micro.mu/docs/images/go-grpc.svg" /> | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| - **Service Discovery** - We make use of go-micro's registry and selector interfaces to provide pluggable discovery  | ||||
| and client side load balancing. There's no need to dial connections, we'll do everything beneath the covers for you. | ||||
|  | ||||
| - **PubSub Messaging** - Where gRPC only provides you synchronous communication, **Go gRPC** uses the go-micro broker  | ||||
| to provide asynchronous messaging while using the gRPC protocol. | ||||
|  | ||||
| - **Micro Ecosystem** - Make use of the existing micro ecosystem of tooling including our api gateway, web dashboard,  | ||||
| command line interface and much more. We're enhancing gRPC with a simplified experience using micro. | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| Find an example greeter service in [examples/greeter](https://github.com/micro/go-micro/service/grpc/tree/master/examples/greeter). | ||||
|  | ||||
| ## Getting Started | ||||
|  | ||||
| See the [docs](https://micro.mu/docs/go-grpc.html) to get started. | ||||
|  | ||||
| ## I18n | ||||
|  | ||||
| ### [中文](README_cn.md) | ||||
							
								
								
									
										25
									
								
								service/grpc/README_cn.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								service/grpc/README_cn.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| # Micro gRPC [](https://opensource.org/licenses/Apache-2.0) [](https://godoc.org/github.com/micro/go-micro/service/grpc) [](https://travis-ci.org/micro/go-micro/service/grpc) [](https://goreportcard.com/report/github.com/micro/go-micro/service/grpc) | ||||
|  | ||||
| Micro gRPC是micro的gRPC框架插件,简化开发基于gRPC的服务。 | ||||
|  | ||||
| ## 概览 | ||||
|  | ||||
| micro提供有基于Go的gRPC插件[go-micro](https://github.com/micro/go-micro),该插件可以在内部集成gPRC,并与之无缝交互,让开发gRPC更简单,并支持[grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway)。 | ||||
|  | ||||
| micro有面向gRPC的[客户端](https://github.com/micro/go-plugins/tree/master/client)和[服务端](https://github.com/micro/go-plugins/tree/master/server)插件,go-grpc库调用客户端/服务端插件生成micro需要的gRPC代码,而客户端/服务端插件都是从[github.com/grpc/grpc-go](https://github.com/grpc/grpc-go)扩展而来,也即是说,我们不需要去知道go-micro是如何编解码或传输就可以使用原生的gRPC。 | ||||
|  | ||||
| ## 特性 | ||||
|  | ||||
| - **服务发现** - go-micro的服务发现基于其[注册](https://github.com/micro/go-plugins/tree/master/registry)与[选择器](https://github.com/micro/go-micro/tree/master/selector)接口,实现了可插拔的服务发现与客户端侧的负载均衡,不需要拨号连接,micro已经把所有都封装好,大家只管用。 | ||||
|  | ||||
| - **消息发布订阅** - 因为gRPC只提供同步通信机制,而**Go gRPC**使用go-micro的[broker代理](https://github.com/micro/go-micro/tree/master/broker)提供异步消息,broker也是基于gRPC协议。 | ||||
|  | ||||
| - **Micro生态系统** - Micro生态系统包含工具链中,比如api网关、web管理控制台、CLI命令行接口等等。我们通过使用micro来增强gRPC框架的易用性。 | ||||
|  | ||||
| ## 示例 | ||||
|  | ||||
| 示例请查看[examples/greeter](https://github.com/micro/go-micro/service/grpc/tree/master/examples/greeter)。 | ||||
|  | ||||
| ## 开始使用 | ||||
|  | ||||
| 我们提供相关文档[docs](https://micro.mu/docs/go-grpc_cn.html),以便上手。 | ||||
							
								
								
									
										64
									
								
								service/grpc/examples/greeter/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								service/grpc/examples/greeter/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| # Greeter Service | ||||
|  | ||||
| An example Go-Micro based gRPC service | ||||
|  | ||||
| ## What's here? | ||||
|  | ||||
| - **server** - a gRPC greeter service | ||||
| - **client** - a gRPC client that calls the service once | ||||
| - **function** - a gRPC greeter function,more about [Function](https://micro.mu/docs/writing-a-go-function.html) | ||||
| - **gateway** - a grpc-gateway | ||||
|  | ||||
| ## Test Service | ||||
|  | ||||
| Run Service | ||||
| ``` | ||||
| $ go run server/main.go --registry=mdns | ||||
| 2016/11/03 18:41:22 Listening on [::]:55194 | ||||
| 2016/11/03 18:41:22 Broker Listening on [::]:55195 | ||||
| 2016/11/03 18:41:22 Registering node: go.micro.srv.greeter-1e200612-a1f5-11e6-8e84-68a86d0d36b6 | ||||
| ``` | ||||
|  | ||||
| Test Service | ||||
| ``` | ||||
| $ go run client/main.go --registry=mdns | ||||
| Hello John | ||||
| ``` | ||||
|  | ||||
| ## Test Function | ||||
|  | ||||
| Run function | ||||
|  | ||||
| ``` | ||||
| go run function/main.go --registry=mdns | ||||
| ``` | ||||
|  | ||||
| Query function | ||||
|  | ||||
| ``` | ||||
| go run client/main.go --registry=mdns --service_name="go.micro.fnc.greeter" | ||||
| ``` | ||||
|  | ||||
| ## Test Gateway | ||||
|  | ||||
| Run server with address set | ||||
|  | ||||
| ``` | ||||
| go run server/main.go --registry=mdns --server_address=localhost:9090 | ||||
| ``` | ||||
|  | ||||
| Run gateway | ||||
|  | ||||
| ``` | ||||
| go run gateway/main.go | ||||
| ``` | ||||
|  | ||||
| Curl gateway | ||||
|  | ||||
| ``` | ||||
| curl -d '{"name": "john"}' http://localhost:8080/greeter/hello | ||||
| ``` | ||||
|  | ||||
| ## i18n | ||||
|  | ||||
| ### [中文](README_cn.md) | ||||
							
								
								
									
										76
									
								
								service/grpc/examples/greeter/README_cn.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								service/grpc/examples/greeter/README_cn.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| # Greeter 问候示例服务 | ||||
|  | ||||
| 本示例展示基于gRPC的Go-Micro服务 | ||||
|  | ||||
| ## 本目录有 | ||||
|  | ||||
| - **server** - Greeter的gRPC服务端 | ||||
| - **client** - gRPC客户端,会调用一次server | ||||
| - **function** - 演示gRPC Greeter function接口,更多关于Function,请查阅[Function](https://micro.mu/docs/writing-a-go-function_cn.html) | ||||
| - **gateway** - gRPC网关 | ||||
|  | ||||
| ## 测试服务 | ||||
|  | ||||
| 运行服务 | ||||
|  | ||||
| ``` | ||||
| $ go run server/main.go --registry=mdns | ||||
| 2016/11/03 18:41:22 Listening on [::]:55194 | ||||
| 2016/11/03 18:41:22 Broker Listening on [::]:55195 | ||||
| 2016/11/03 18:41:22 Registering node: go.micro.srv.greeter-1e200612-a1f5-11e6-8e84-68a86d0d36b6 | ||||
| ``` | ||||
|  | ||||
| 测试 | ||||
|  | ||||
| ``` | ||||
| $ go run client/main.go --registry=mdns | ||||
| Hello John | ||||
| ``` | ||||
|  | ||||
| ## 测试 Function | ||||
|  | ||||
| 运行测试 | ||||
|  | ||||
| ``` | ||||
| go run function/main.go --registry=mdns | ||||
| ``` | ||||
|  | ||||
| 调用服务 | ||||
|  | ||||
| 服务端的Function服务只会执行一次,所以在下面的命令执行且服务端返回请求后,服务端便后退出 | ||||
|  | ||||
| ```bash | ||||
| $ go run client/main.go --registry=mdns --service_name="go.micro.fnc.greeter" | ||||
|  | ||||
| # 返回 | ||||
| Hello John | ||||
|  | ||||
| # 再次执行 | ||||
| $ go run client/main.go --registry=mdns --service_name="go.micro.fnc.greeter" | ||||
|  | ||||
| # 就会报异常,找不到服务 | ||||
| {"id":"go.micro.client","code":500,"detail":"none available","status":"Internal Server Error"} | ||||
|  | ||||
| ``` | ||||
|  | ||||
| ## 测试网关 | ||||
|  | ||||
| 指定地址再运行服务端: | ||||
|  | ||||
| ``` | ||||
| go run server/main.go --registry=mdns --server_address=localhost:9090 | ||||
| ``` | ||||
|  | ||||
| 运行网关 | ||||
|  | ||||
| ``` | ||||
| go run gateway/main.go | ||||
| ``` | ||||
|  | ||||
| 使用curl调用网关 | ||||
|  | ||||
| ``` | ||||
| curl -d '{"name": "john"}' http://localhost:8080/greeter/hello | ||||
| # 返回 | ||||
| {"msg":"Hello john"} | ||||
| ``` | ||||
							
								
								
									
										40
									
								
								service/grpc/examples/greeter/client/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								service/grpc/examples/greeter/client/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/micro/cli" | ||||
| 	"github.com/micro/go-micro" | ||||
| 	"github.com/micro/go-micro/service/grpc" | ||||
| 	hello "github.com/micro/go-micro/service/grpc/examples/greeter/server/proto/hello" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// service to call | ||||
| 	serviceName string | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	service := grpc.NewService() | ||||
|  | ||||
| 	service.Init( | ||||
| 		micro.Flags(cli.StringFlag{ | ||||
| 			Name:        "service_name", | ||||
| 			Value:       "go.micro.srv.greeter", | ||||
| 			Destination: &serviceName, | ||||
| 		}), | ||||
| 	) | ||||
|  | ||||
| 	cl := hello.NewSayService(serviceName, service.Client()) | ||||
|  | ||||
| 	rsp, err := cl.Hello(context.TODO(), &hello.Request{ | ||||
| 		Name: "John", | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		fmt.Println(err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	fmt.Println(rsp.Msg) | ||||
| } | ||||
							
								
								
									
										31
									
								
								service/grpc/examples/greeter/function/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								service/grpc/examples/greeter/function/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"log" | ||||
|  | ||||
| 	"github.com/micro/go-micro" | ||||
| 	"github.com/micro/go-micro/service/grpc" | ||||
| 	hello "github.com/micro/go-micro/service/grpc/examples/greeter/server/proto/hello" | ||||
| ) | ||||
|  | ||||
| type Say struct{} | ||||
|  | ||||
| func (s *Say) Hello(ctx context.Context, req *hello.Request, rsp *hello.Response) error { | ||||
| 	rsp.Msg = "Hello " + req.Name | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	fn := grpc.NewFunction( | ||||
| 		micro.Name("go.micro.fnc.greeter"), | ||||
| 	) | ||||
|  | ||||
| 	fn.Init() | ||||
|  | ||||
| 	fn.Handle(new(Say)) | ||||
|  | ||||
| 	if err := fn.Run(); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
| @@ -0,0 +1,99 @@ | ||||
| // Code generated by protoc-gen-micro. DO NOT EDIT. | ||||
| // source: github.com/micro/go-micro/service/grpc/examples/greeter/function/proto/hello/hello.proto | ||||
|  | ||||
| /* | ||||
| Package go_micro_srv_greeter is a generated protocol buffer package. | ||||
|  | ||||
| It is generated from these files: | ||||
| 	github.com/micro/go-micro/service/grpc/examples/greeter/function/proto/hello/hello.proto | ||||
|  | ||||
| It has these top-level messages: | ||||
| 	Request | ||||
| 	Response | ||||
| */ | ||||
| package go_micro_srv_greeter | ||||
|  | ||||
| import proto "github.com/golang/protobuf/proto" | ||||
| import fmt "fmt" | ||||
| import math "math" | ||||
|  | ||||
| import ( | ||||
| 	context "context" | ||||
| 	client "github.com/micro/go-micro/client" | ||||
| 	server "github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| // Reference imports to suppress errors if they are not otherwise used. | ||||
| var _ = proto.Marshal | ||||
| var _ = fmt.Errorf | ||||
| var _ = math.Inf | ||||
|  | ||||
| // This is a compile-time assertion to ensure that this generated file | ||||
| // is compatible with the proto package it is being compiled against. | ||||
| // A compilation error at this line likely means your copy of the | ||||
| // proto package needs to be updated. | ||||
| const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package | ||||
|  | ||||
| // Reference imports to suppress errors if they are not otherwise used. | ||||
| var _ context.Context | ||||
| var _ client.Option | ||||
| var _ server.Option | ||||
|  | ||||
| // Client API for Say service | ||||
|  | ||||
| type SayService interface { | ||||
| 	Hello(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) | ||||
| } | ||||
|  | ||||
| type sayService struct { | ||||
| 	c           client.Client | ||||
| 	serviceName string | ||||
| } | ||||
|  | ||||
| func NewSayService(serviceName string, c client.Client) SayService { | ||||
| 	if c == nil { | ||||
| 		c = client.NewClient() | ||||
| 	} | ||||
| 	if len(serviceName) == 0 { | ||||
| 		serviceName = "go.micro.srv.greeter" | ||||
| 	} | ||||
| 	return &sayService{ | ||||
| 		c:           c, | ||||
| 		serviceName: serviceName, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *sayService) Hello(ctx context.Context, in *Request, opts ...client.CallOption) (*Response, error) { | ||||
| 	req := c.c.NewRequest(c.serviceName, "Say.Hello", in) | ||||
| 	out := new(Response) | ||||
| 	err := c.c.Call(ctx, req, out, opts...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| // Server API for Say service | ||||
|  | ||||
| type SayHandler interface { | ||||
| 	Hello(context.Context, *Request, *Response) error | ||||
| } | ||||
|  | ||||
| func RegisterSayHandler(s server.Server, hdlr SayHandler, opts ...server.HandlerOption) { | ||||
| 	type say interface { | ||||
| 		Hello(ctx context.Context, in *Request, out *Response) error | ||||
| 	} | ||||
| 	type Say struct { | ||||
| 		say | ||||
| 	} | ||||
| 	h := &sayHandler{hdlr} | ||||
| 	s.Handle(s.NewHandler(&Say{h}, opts...)) | ||||
| } | ||||
|  | ||||
| type sayHandler struct { | ||||
| 	SayHandler | ||||
| } | ||||
|  | ||||
| func (h *sayHandler) Hello(ctx context.Context, in *Request, out *Response) error { | ||||
| 	return h.SayHandler.Hello(ctx, in, out) | ||||
| } | ||||
							
								
								
									
										163
									
								
								service/grpc/examples/greeter/function/proto/hello/hello.pb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								service/grpc/examples/greeter/function/proto/hello/hello.pb.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,163 @@ | ||||
| // Code generated by protoc-gen-go. DO NOT EDIT. | ||||
| // source: github.com/micro/go-micro/service/grpc/examples/greeter/function/proto/hello/hello.proto | ||||
|  | ||||
| /* | ||||
| Package go_micro_srv_greeter is a generated protocol buffer package. | ||||
|  | ||||
| It is generated from these files: | ||||
| 	github.com/micro/go-micro/service/grpc/examples/greeter/function/proto/hello/hello.proto | ||||
|  | ||||
| It has these top-level messages: | ||||
| 	Request | ||||
| 	Response | ||||
| */ | ||||
| package go_micro_srv_greeter | ||||
|  | ||||
| import proto "github.com/golang/protobuf/proto" | ||||
| import fmt "fmt" | ||||
| import math "math" | ||||
|  | ||||
| import ( | ||||
| 	context "golang.org/x/net/context" | ||||
| 	grpc "google.golang.org/grpc" | ||||
| ) | ||||
|  | ||||
| // Reference imports to suppress errors if they are not otherwise used. | ||||
| var _ = proto.Marshal | ||||
| var _ = fmt.Errorf | ||||
| var _ = math.Inf | ||||
|  | ||||
| // This is a compile-time assertion to ensure that this generated file | ||||
| // is compatible with the proto package it is being compiled against. | ||||
| // A compilation error at this line likely means your copy of the | ||||
| // proto package needs to be updated. | ||||
| const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package | ||||
|  | ||||
| type Request struct { | ||||
| 	Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` | ||||
| } | ||||
|  | ||||
| func (m *Request) Reset()                    { *m = Request{} } | ||||
| func (m *Request) String() string            { return proto.CompactTextString(m) } | ||||
| func (*Request) ProtoMessage()               {} | ||||
| func (*Request) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } | ||||
|  | ||||
| func (m *Request) GetName() string { | ||||
| 	if m != nil { | ||||
| 		return m.Name | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| type Response struct { | ||||
| 	Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"` | ||||
| } | ||||
|  | ||||
| func (m *Response) Reset()                    { *m = Response{} } | ||||
| func (m *Response) String() string            { return proto.CompactTextString(m) } | ||||
| func (*Response) ProtoMessage()               {} | ||||
| func (*Response) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } | ||||
|  | ||||
| func (m *Response) GetMsg() string { | ||||
| 	if m != nil { | ||||
| 		return m.Msg | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	proto.RegisterType((*Request)(nil), "go.micro.srv.greeter.Request") | ||||
| 	proto.RegisterType((*Response)(nil), "go.micro.srv.greeter.Response") | ||||
| } | ||||
|  | ||||
| // Reference imports to suppress errors if they are not otherwise used. | ||||
| var _ context.Context | ||||
| var _ grpc.ClientConn | ||||
|  | ||||
| // This is a compile-time assertion to ensure that this generated file | ||||
| // is compatible with the grpc package it is being compiled against. | ||||
| const _ = grpc.SupportPackageIsVersion4 | ||||
|  | ||||
| // Client API for Say service | ||||
|  | ||||
| type SayClient interface { | ||||
| 	Hello(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) | ||||
| } | ||||
|  | ||||
| type sayClient struct { | ||||
| 	cc *grpc.ClientConn | ||||
| } | ||||
|  | ||||
| func NewSayClient(cc *grpc.ClientConn) SayClient { | ||||
| 	return &sayClient{cc} | ||||
| } | ||||
|  | ||||
| func (c *sayClient) Hello(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { | ||||
| 	out := new(Response) | ||||
| 	err := grpc.Invoke(ctx, "/go.micro.srv.greeter.Say/Hello", in, out, c.cc, opts...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| // Server API for Say service | ||||
|  | ||||
| type SayServer interface { | ||||
| 	Hello(context.Context, *Request) (*Response, error) | ||||
| } | ||||
|  | ||||
| func RegisterSayServer(s *grpc.Server, srv SayServer) { | ||||
| 	s.RegisterService(&_Say_serviceDesc, srv) | ||||
| } | ||||
|  | ||||
| func _Say_Hello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | ||||
| 	in := new(Request) | ||||
| 	if err := dec(in); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if interceptor == nil { | ||||
| 		return srv.(SayServer).Hello(ctx, in) | ||||
| 	} | ||||
| 	info := &grpc.UnaryServerInfo{ | ||||
| 		Server:     srv, | ||||
| 		FullMethod: "/go.micro.srv.greeter.Say/Hello", | ||||
| 	} | ||||
| 	handler := func(ctx context.Context, req interface{}) (interface{}, error) { | ||||
| 		return srv.(SayServer).Hello(ctx, req.(*Request)) | ||||
| 	} | ||||
| 	return interceptor(ctx, in, info, handler) | ||||
| } | ||||
|  | ||||
| var _Say_serviceDesc = grpc.ServiceDesc{ | ||||
| 	ServiceName: "go.micro.srv.greeter.Say", | ||||
| 	HandlerType: (*SayServer)(nil), | ||||
| 	Methods: []grpc.MethodDesc{ | ||||
| 		{ | ||||
| 			MethodName: "Hello", | ||||
| 			Handler:    _Say_Hello_Handler, | ||||
| 		}, | ||||
| 	}, | ||||
| 	Streams:  []grpc.StreamDesc{}, | ||||
| 	Metadata: "github.com/micro/go-micro/service/grpc/examples/greeter/function/proto/hello/hello.proto", | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	proto.RegisterFile("github.com/micro/go-micro/service/grpc/examples/greeter/function/proto/hello/hello.proto", fileDescriptor0) | ||||
| } | ||||
|  | ||||
| var fileDescriptor0 = []byte{ | ||||
| 	// 187 bytes of a gzipped FileDescriptorProto | ||||
| 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x8e, 0x4d, 0x0a, 0xc2, 0x30, | ||||
| 	0x10, 0x85, 0x2d, 0xf5, 0x37, 0x2b, 0x09, 0x2e, 0x44, 0xac, 0x48, 0x57, 0x6e, 0x4c, 0x40, 0x2f, | ||||
| 	0x51, 0xdc, 0x08, 0xf5, 0x04, 0x6d, 0x18, 0xd3, 0x42, 0x93, 0x89, 0x49, 0x2a, 0x7a, 0x7b, 0x69, | ||||
| 	0xcc, 0x52, 0x37, 0xc3, 0x63, 0x3e, 0x66, 0xbe, 0x47, 0x2e, 0xb2, 0xf5, 0x4d, 0x5f, 0x33, 0x81, | ||||
| 	0x8a, 0xab, 0x56, 0x58, 0xe4, 0x12, 0x8f, 0xd2, 0x1a, 0xc1, 0xe1, 0x55, 0x29, 0xd3, 0x81, 0xe3, | ||||
| 	0xd2, 0x02, 0x78, 0xb0, 0xfc, 0xde, 0x6b, 0xe1, 0x5b, 0xd4, 0xdc, 0x58, 0xf4, 0xc8, 0x1b, 0xe8, | ||||
| 	0xba, 0x38, 0x59, 0xd8, 0xd0, 0x95, 0x44, 0x16, 0x7e, 0x30, 0x67, 0x9f, 0x2c, 0x9e, 0xe5, 0x19, | ||||
| 	0x99, 0x95, 0xf0, 0xe8, 0xc1, 0x79, 0x4a, 0xc9, 0x58, 0x57, 0x0a, 0xd6, 0xc9, 0x3e, 0x39, 0x2c, | ||||
| 	0xca, 0x90, 0xf3, 0x2d, 0x99, 0x97, 0xe0, 0x0c, 0x6a, 0x07, 0x74, 0x49, 0x52, 0xe5, 0x64, 0xc4, | ||||
| 	0x43, 0x3c, 0x5d, 0x49, 0x7a, 0xab, 0xde, 0xb4, 0x20, 0x93, 0x62, 0x10, 0xd1, 0x8c, 0xfd, 0x72, | ||||
| 	0xb0, 0x28, 0xd8, 0xec, 0xfe, 0xe1, 0xaf, 0x20, 0x1f, 0xd5, 0xd3, 0x50, 0xf5, 0xfc, 0x09, 0x00, | ||||
| 	0x00, 0xff, 0xff, 0x79, 0x62, 0x76, 0xe6, 0xf8, 0x00, 0x00, 0x00, | ||||
| } | ||||
| @@ -0,0 +1,15 @@ | ||||
| syntax = "proto3"; | ||||
|  | ||||
| package go.micro.srv.greeter; | ||||
|  | ||||
| service Say { | ||||
| 	rpc Hello(Request) returns (Response) {} | ||||
| } | ||||
|  | ||||
| message Request { | ||||
| 	string name = 1; | ||||
| } | ||||
|  | ||||
| message Response { | ||||
| 	string msg = 1; | ||||
| } | ||||
							
								
								
									
										29
									
								
								service/grpc/examples/greeter/gateway/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								service/grpc/examples/greeter/gateway/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| # GRPC Gateway | ||||
|  | ||||
| This directory contains a grpc gateway generated using [grpc-ecosystem/grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway). | ||||
|  | ||||
| Services written with [micro/go-micro/service/grpc](https://github.com/micro/go-micro/service/grpc) are fully compatible with the grpc-gateway and any other  | ||||
| grpc services. | ||||
|  | ||||
| Go to [grpc-ecosystem/grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) for details on how to generate gateways. We  | ||||
| have generated the gateway from the same proto as the greeter server but with additional options for the gateway. | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
| Run the go.micro.srv.greeter service | ||||
|  | ||||
| ``` | ||||
| go run ../server/main.go --server_address=localhost:9090 | ||||
| ``` | ||||
|  | ||||
| Run the gateway | ||||
|  | ||||
| ``` | ||||
| go run main.go | ||||
| ``` | ||||
|  | ||||
| Curl your request at the gateway (localhost:8080) | ||||
|  | ||||
| ``` | ||||
| curl -d '{"name": "john"}' http://localhost:8080/greeter/hello | ||||
| ``` | ||||
							
								
								
									
										44
									
								
								service/grpc/examples/greeter/gateway/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								service/grpc/examples/greeter/gateway/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"flag" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/golang/glog" | ||||
| 	"github.com/grpc-ecosystem/grpc-gateway/runtime" | ||||
| 	"google.golang.org/grpc" | ||||
|  | ||||
| 	hello "github.com/micro/examples/grpc/gateway/proto/hello" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// the go.micro.srv.greeter address | ||||
| 	endpoint = flag.String("endpoint", "localhost:9090", "go.micro.srv.greeter address") | ||||
| ) | ||||
|  | ||||
| func run() error { | ||||
| 	ctx := context.Background() | ||||
| 	ctx, cancel := context.WithCancel(ctx) | ||||
| 	defer cancel() | ||||
|  | ||||
| 	mux := runtime.NewServeMux() | ||||
| 	opts := []grpc.DialOption{grpc.WithInsecure()} | ||||
|  | ||||
| 	err := hello.RegisterSayHandlerFromEndpoint(ctx, mux, *endpoint, opts) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return http.ListenAndServe(":8080", mux) | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	flag.Parse() | ||||
|  | ||||
| 	defer glog.Flush() | ||||
|  | ||||
| 	if err := run(); err != nil { | ||||
| 		glog.Fatal(err) | ||||
| 	} | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user