Add http server which implements go-micro.Server
This commit is contained in:
		
							
								
								
									
										65
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| # HTTP Server | ||||
|  | ||||
| The HTTP Server is a go-micro.Server. It's a partial implementation which strips out codecs, transports, etc but enables you | ||||
| to create a HTTP Server that could potentially be used for REST based API services. | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
| ```go | ||||
| import ( | ||||
|         "net/http" | ||||
|  | ||||
|         "github.com/micro/go-micro/server" | ||||
|         httpServer "github.com/micro/go-plugins/server/http" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
|         srv := httpServer.NewServer( | ||||
|                 server.Name("helloworld"), | ||||
|         ) | ||||
|  | ||||
|         mux := http.NewServeMux() | ||||
|         mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||||
|                 w.Write([]byte(`hello world`)) | ||||
|         }) | ||||
|  | ||||
|         hd := srv.NewHandler(mux) | ||||
|  | ||||
|         srv.Handle(hd) | ||||
| 	srv.Start() | ||||
| 	srv.Register() | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Or as part of a service | ||||
|  | ||||
| ```go | ||||
| import ( | ||||
|         "net/http" | ||||
|  | ||||
|         "github.com/micro/go-micro" | ||||
|         "github.com/micro/go-micro/server" | ||||
|         httpServer "github.com/micro/go-plugins/server/http" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
|         srv := httpServer.NewServer( | ||||
|                 server.Name("helloworld"), | ||||
|         ) | ||||
|  | ||||
|         mux := http.NewServeMux() | ||||
|         mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||||
|                 w.Write([]byte(`hello world`)) | ||||
|         }) | ||||
|  | ||||
|         hd := srv.NewHandler(mux) | ||||
|  | ||||
|         srv.Handle(hd) | ||||
|  | ||||
|         service := micro.NewService( | ||||
|                 micro.Server(srv), | ||||
|         ) | ||||
| 	service.Init() | ||||
|         service.Run() | ||||
| } | ||||
| ``` | ||||
							
								
								
									
										114
									
								
								extractor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								extractor.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| package http | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	privateBlocks []*net.IPNet | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	for _, b := range []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"} { | ||||
| 		if _, block, err := net.ParseCIDR(b); err == nil { | ||||
| 			privateBlocks = append(privateBlocks, block) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func extractAddress(addr string) (string, error) { | ||||
| 	if len(addr) > 0 && (addr != "0.0.0.0" && addr != "[::]") { | ||||
| 		return addr, nil | ||||
| 	} | ||||
|  | ||||
| 	addrs, err := net.InterfaceAddrs() | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("Failed to get interface addresses! Err: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	var ipAddr []byte | ||||
|  | ||||
| 	for _, rawAddr := range addrs { | ||||
| 		var ip net.IP | ||||
| 		switch addr := rawAddr.(type) { | ||||
| 		case *net.IPAddr: | ||||
| 			ip = addr.IP | ||||
| 		case *net.IPNet: | ||||
| 			ip = addr.IP | ||||
| 		default: | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if ip.To4() == nil { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if !isPrivateIP(ip.String()) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		ipAddr = ip | ||||
| 		break | ||||
| 	} | ||||
|  | ||||
| 	if ipAddr == nil { | ||||
| 		return "", fmt.Errorf("No private IP address found, and explicit IP not provided") | ||||
| 	} | ||||
|  | ||||
| 	return net.IP(ipAddr).String(), nil | ||||
| } | ||||
|  | ||||
| func isPrivateIP(ipAddr string) bool { | ||||
| 	ip := net.ParseIP(ipAddr) | ||||
| 	for _, priv := range privateBlocks { | ||||
| 		if priv.Contains(ip) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func serviceDef(opts server.Options) *registry.Service { | ||||
| 	var advt, host string | ||||
| 	var port int | ||||
|  | ||||
| 	if len(opts.Advertise) > 0 { | ||||
| 		advt = opts.Advertise | ||||
| 	} else { | ||||
| 		advt = opts.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 := extractAddress(host) | ||||
| 	if err != nil { | ||||
| 		addr = host | ||||
| 	} | ||||
|  | ||||
| 	node := ®istry.Node{ | ||||
| 		Id:       opts.Name + "-" + opts.Id, | ||||
| 		Address:  addr, | ||||
| 		Port:     port, | ||||
| 		Metadata: opts.Metadata, | ||||
| 	} | ||||
|  | ||||
| 	node.Metadata["server"] = "http" | ||||
|  | ||||
| 	return ®istry.Service{ | ||||
| 		Name:    opts.Name, | ||||
| 		Version: opts.Version, | ||||
| 		Nodes:   []*registry.Node{node}, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										27
									
								
								handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								handler.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| package http | ||||
|  | ||||
| import ( | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| type httpHandler struct { | ||||
| 	opts server.HandlerOptions | ||||
| 	hd   interface{} | ||||
| } | ||||
|  | ||||
| func (h *httpHandler) Name() string { | ||||
| 	return "handler" | ||||
| } | ||||
|  | ||||
| func (h *httpHandler) Handler() interface{} { | ||||
| 	return h.hd | ||||
| } | ||||
|  | ||||
| func (h *httpHandler) Endpoints() []*registry.Endpoint { | ||||
| 	return []*registry.Endpoint{} | ||||
| } | ||||
|  | ||||
| func (h *httpHandler) Options() server.HandlerOptions { | ||||
| 	return h.opts | ||||
| } | ||||
							
								
								
									
										148
									
								
								http.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								http.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | ||||
| // Package http implements a go-micro.Server | ||||
| package http | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| type httpServer struct { | ||||
| 	sync.Mutex | ||||
| 	opts server.Options | ||||
| 	hd   server.Handler | ||||
| 	exit chan chan error | ||||
| } | ||||
|  | ||||
| func (h *httpServer) Options() server.Options { | ||||
| 	h.Lock() | ||||
| 	opts := h.opts | ||||
| 	h.Unlock() | ||||
| 	return opts | ||||
| } | ||||
|  | ||||
| func (h *httpServer) Init(opts ...server.Option) error { | ||||
| 	h.Lock() | ||||
| 	for _, o := range opts { | ||||
| 		o(&h.opts) | ||||
| 	} | ||||
| 	h.Unlock() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (h *httpServer) Handle(handler server.Handler) error { | ||||
| 	if _, ok := handler.Handler().(http.Handler); !ok { | ||||
| 		return errors.New("Handle requires http.Handler") | ||||
| 	} | ||||
| 	h.Lock() | ||||
| 	h.hd = handler | ||||
| 	h.Unlock() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (h *httpServer) NewHandler(handler interface{}, opts ...server.HandlerOption) server.Handler { | ||||
| 	var options server.HandlerOptions | ||||
| 	for _, o := range opts { | ||||
| 		o(&options) | ||||
| 	} | ||||
|  | ||||
| 	return &httpHandler{ | ||||
| 		opts: options, | ||||
| 		hd:   handler, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (h *httpServer) NewSubscriber(topic string, handler interface{}, opts ...server.SubscriberOption) server.Subscriber { | ||||
| 	var options server.SubscriberOptions | ||||
| 	for _, o := range opts { | ||||
| 		o(&options) | ||||
| 	} | ||||
|  | ||||
| 	return &httpSubscriber{ | ||||
| 		opts:  options, | ||||
| 		topic: topic, | ||||
| 		hd:    handler, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (h *httpServer) Subscribe(s server.Subscriber) error { | ||||
| 	return errors.New("subscribe is not supported") | ||||
| } | ||||
|  | ||||
| func (h *httpServer) Register() error { | ||||
| 	h.Lock() | ||||
| 	opts := h.opts | ||||
| 	h.Unlock() | ||||
|  | ||||
| 	service := serviceDef(opts) | ||||
|  | ||||
| 	rOpts := []registry.RegisterOption{ | ||||
| 		registry.RegisterTTL(opts.RegisterTTL), | ||||
| 	} | ||||
|  | ||||
| 	return opts.Registry.Register(service, rOpts...) | ||||
| } | ||||
|  | ||||
| func (h *httpServer) Deregister() error { | ||||
| 	h.Lock() | ||||
| 	opts := h.opts | ||||
| 	h.Unlock() | ||||
|  | ||||
| 	service := serviceDef(opts) | ||||
| 	return opts.Registry.Deregister(service) | ||||
| } | ||||
|  | ||||
| func (h *httpServer) Start() error { | ||||
| 	h.Lock() | ||||
| 	opts := h.opts | ||||
| 	hd := h.hd | ||||
| 	h.Unlock() | ||||
|  | ||||
| 	ln, err := net.Listen("tcp", opts.Address) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	h.Lock() | ||||
| 	h.opts.Address = ln.Addr().String() | ||||
| 	h.Unlock() | ||||
|  | ||||
| 	handler, ok := hd.Handler().(http.Handler) | ||||
| 	if !ok { | ||||
| 		return errors.New("Server required http.Handler") | ||||
| 	} | ||||
|  | ||||
| 	go http.Serve(ln, handler) | ||||
|  | ||||
| 	go func() { | ||||
| 		ch := <-h.exit | ||||
| 		ch <- ln.Close() | ||||
| 	}() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (h *httpServer) Stop() error { | ||||
| 	ch := make(chan error) | ||||
| 	h.exit <- ch | ||||
| 	return <-ch | ||||
| } | ||||
|  | ||||
| func (h *httpServer) String() string { | ||||
| 	return "http" | ||||
| } | ||||
|  | ||||
| func newServer(opts ...server.Option) server.Server { | ||||
| 	return &httpServer{ | ||||
| 		opts: newOptions(opts...), | ||||
| 		exit: make(chan chan error), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func NewServer(opts ...server.Option) server.Server { | ||||
| 	return newServer(opts...) | ||||
| } | ||||
							
								
								
									
										89
									
								
								http_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								http_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| package http | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/registry/mock" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| func TestHTTPServer(t *testing.T) { | ||||
| 	reg := mock.NewRegistry() | ||||
|  | ||||
| 	// create server | ||||
| 	srv := NewServer(server.Registry(reg)) | ||||
|  | ||||
| 	// create server mux | ||||
| 	mux := http.NewServeMux() | ||||
| 	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||||
| 		w.Write([]byte(`hello world`)) | ||||
| 	}) | ||||
|  | ||||
| 	// create handler | ||||
| 	hd := srv.NewHandler(mux) | ||||
|  | ||||
| 	// register handler | ||||
| 	if err := srv.Handle(hd); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// start server | ||||
| 	if err := srv.Start(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// register server | ||||
| 	if err := srv.Register(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// lookup server | ||||
| 	service, err := reg.GetService(server.DefaultName) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if len(service) != 1 { | ||||
| 		t.Fatalf("Expected 1 service got %d: %+v", len(service), service) | ||||
| 	} | ||||
|  | ||||
| 	if len(service[0].Nodes) != 1 { | ||||
| 		t.Fatalf("Expected 1 node got %d: %+v", len(service[0].Nodes), service[0].Nodes) | ||||
| 	} | ||||
|  | ||||
| 	// make request | ||||
| 	rsp, err := http.Get(fmt.Sprintf("http://%s:%d", service[0].Nodes[0].Address, service[0].Nodes[0].Port)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer rsp.Body.Close() | ||||
|  | ||||
| 	b, err := ioutil.ReadAll(rsp.Body) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if s := string(b); s != "hello world" { | ||||
| 		t.Fatalf("Expected response %s, got %s", "hello world", s) | ||||
| 	} | ||||
|  | ||||
| 	// deregister server | ||||
| 	if err := srv.Deregister(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// try get service | ||||
| 	service, err = reg.GetService(server.DefaultName) | ||||
| 	if err == nil { | ||||
| 		t.Fatal("Expected %v got %+v", registry.ErrNotFound, service) | ||||
| 	} | ||||
|  | ||||
| 	// stop server | ||||
| 	if err := srv.Stop(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										43
									
								
								options.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								options.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| package http | ||||
|  | ||||
| import ( | ||||
| 	"github.com/micro/go-micro/codec" | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/server" | ||||
|  | ||||
| 	"golang.org/x/net/context" | ||||
| ) | ||||
|  | ||||
| func newOptions(opt ...server.Option) server.Options { | ||||
| 	opts := server.Options{ | ||||
| 		Codecs:   make(map[string]codec.NewCodec), | ||||
| 		Metadata: map[string]string{}, | ||||
| 		Context:  context.Background(), | ||||
| 	} | ||||
|  | ||||
| 	for _, o := range opt { | ||||
| 		o(&opts) | ||||
| 	} | ||||
|  | ||||
| 	if opts.Registry == nil { | ||||
| 		opts.Registry = registry.DefaultRegistry | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
| } | ||||
							
								
								
									
										28
									
								
								subscriber.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								subscriber.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| package http | ||||
|  | ||||
| import ( | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/go-micro/server" | ||||
| ) | ||||
|  | ||||
| type httpSubscriber struct { | ||||
| 	opts  server.SubscriberOptions | ||||
| 	topic string | ||||
| 	hd    interface{} | ||||
| } | ||||
|  | ||||
| func (h *httpSubscriber) Topic() string { | ||||
| 	return h.topic | ||||
| } | ||||
|  | ||||
| func (h *httpSubscriber) Subscriber() interface{} { | ||||
| 	return h.hd | ||||
| } | ||||
|  | ||||
| func (h *httpSubscriber) Endpoints() []*registry.Endpoint { | ||||
| 	return []*registry.Endpoint{} | ||||
| } | ||||
|  | ||||
| func (h *httpSubscriber) Options() server.SubscriberOptions { | ||||
| 	return h.opts | ||||
| } | ||||
		Reference in New Issue
	
	Block a user