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