diff --git a/web/options.go b/web/options.go deleted file mode 100644 index 200050f4..00000000 --- a/web/options.go +++ /dev/null @@ -1,236 +0,0 @@ -package web - -import ( - "context" - "crypto/tls" - "net/http" - "time" - - "github.com/micro/go-micro/v3/registry" - "github.com/micro/go-micro/v3/registry/memory" -) - -//Options for web -type Options struct { - Name string - Version string - Id string - Metadata map[string]string - Address string - Advertise string - - RegisterTTL time.Duration - RegisterInterval time.Duration - - // RegisterCheck runs a check function before registering the service - RegisterCheck func(context.Context) error - - Server *http.Server - Handler http.Handler - - // Alternative Options - Context context.Context - - Registry registry.Registry - - Secure bool - TLSConfig *tls.Config - BeforeStart []func() error - BeforeStop []func() error - AfterStart []func() error - AfterStop []func() error - - // Static directory - StaticDir string - - Signal bool -} - -func newOptions(opts ...Option) Options { - opt := Options{ - Name: DefaultName, - Version: DefaultVersion, - Id: DefaultId, - Address: DefaultAddress, - RegisterTTL: DefaultRegisterTTL, - RegisterInterval: DefaultRegisterInterval, - StaticDir: DefaultStaticDir, - Context: context.TODO(), - Signal: true, - } - - for _, o := range opts { - o(&opt) - } - - if opt.Registry == nil { - opt.Registry = memory.NewRegistry() - } - - if opt.RegisterCheck == nil { - opt.RegisterCheck = DefaultRegisterCheck - } - - return opt -} - -// Name of Web -func Name(n string) Option { - return func(o *Options) { - o.Name = n - } -} - -// Icon specifies an icon url to load in the UI -func Icon(ico string) Option { - return func(o *Options) { - if o.Metadata == nil { - o.Metadata = make(map[string]string) - } - o.Metadata["icon"] = ico - } -} - -//Id for Unique server id -func Id(id string) Option { - return func(o *Options) { - o.Id = id - } -} - -// Version of the service -func Version(v string) Option { - return func(o *Options) { - o.Version = v - } -} - -// Metadata associated with the service -func Metadata(md map[string]string) Option { - return func(o *Options) { - o.Metadata = md - } -} - -// Address to bind to - host:port -func Address(a string) Option { - return func(o *Options) { - o.Address = a - } -} - -//Advertise The address to advertise for discovery - host:port -func Advertise(a string) Option { - return func(o *Options) { - o.Advertise = a - } -} - -// Context specifies a context for the service. -// Can be used to signal shutdown of the service. -// Can be used for extra option values. -func Context(ctx context.Context) Option { - return func(o *Options) { - o.Context = ctx - } -} - -// Registry used for discovery -func Registry(r registry.Registry) Option { - return func(o *Options) { - o.Registry = r - } -} - -//RegisterTTL Register the service with a TTL -func RegisterTTL(t time.Duration) Option { - return func(o *Options) { - o.RegisterTTL = t - } -} - -//RegisterInterval Register the service with at interval -func RegisterInterval(t time.Duration) Option { - return func(o *Options) { - o.RegisterInterval = t - } -} - -//Handler for custom handler -func Handler(h http.Handler) Option { - return func(o *Options) { - o.Handler = h - } -} - -//Server for custom Server -func Server(srv *http.Server) Option { - return func(o *Options) { - o.Server = srv - } -} - -// BeforeStart is executed before the server starts. -func BeforeStart(fn func() error) Option { - return func(o *Options) { - o.BeforeStart = append(o.BeforeStart, fn) - } -} - -// BeforeStop is executed before the server stops. -func BeforeStop(fn func() error) Option { - return func(o *Options) { - o.BeforeStop = append(o.BeforeStop, fn) - } -} - -// AfterStart is executed after server start. -func AfterStart(fn func() error) Option { - return func(o *Options) { - o.AfterStart = append(o.AfterStart, fn) - } -} - -// AfterStop is executed after server stop. -func AfterStop(fn func() error) Option { - return func(o *Options) { - o.AfterStop = append(o.AfterStop, fn) - } -} - -// Secure Use secure communication. If TLSConfig is not specified we use InsecureSkipVerify and generate a self signed cert -func Secure(b bool) Option { - return func(o *Options) { - o.Secure = b - } -} - -// TLSConfig to be used for the transport. -func TLSConfig(t *tls.Config) Option { - return func(o *Options) { - o.TLSConfig = t - } -} - -// StaticDir sets the static file directory. This defaults to ./html -func StaticDir(d string) Option { - return func(o *Options) { - o.StaticDir = d - } -} - -// RegisterCheck run func before registry service -func RegisterCheck(fn func(context.Context) error) Option { - return func(o *Options) { - o.RegisterCheck = fn - } -} - -// HandleSignal toggles automatic installation of the signal handler that -// traps TERM, INT, and QUIT. Users of this feature to disable the signal -// handler, should control liveness of the service through the context. -func HandleSignal(b bool) Option { - return func(o *Options) { - o.Signal = b - } -} diff --git a/web/service.go b/web/service.go deleted file mode 100644 index 227cd084..00000000 --- a/web/service.go +++ /dev/null @@ -1,458 +0,0 @@ -package web - -import ( - "crypto/tls" - "fmt" - "net" - "net/http" - "os" - "os/signal" - "path/filepath" - "strings" - "sync" - "time" - - "github.com/micro/go-micro/v3/logger" - "github.com/micro/go-micro/v3/registry" - maddr "github.com/micro/go-micro/v3/util/addr" - "github.com/micro/go-micro/v3/util/backoff" - mnet "github.com/micro/go-micro/v3/util/net" - signalutil "github.com/micro/go-micro/v3/util/signal" - mls "github.com/micro/go-micro/v3/util/tls" -) - -type service struct { - opts Options - - mux *http.ServeMux - srv *registry.Service - - sync.RWMutex - running bool - static bool - exit chan chan error -} - -func newService(opts ...Option) Service { - options := newOptions(opts...) - s := &service{ - opts: options, - mux: http.NewServeMux(), - static: true, - } - s.srv = s.genSrv() - return s -} - -func (s *service) genSrv() *registry.Service { - var host string - var port string - var err error - - // default host:port - if len(s.opts.Address) > 0 { - host, port, err = net.SplitHostPort(s.opts.Address) - if err != nil { - logger.Fatal(err) - } - } - - // check the advertise address first - // if it exists then use it, otherwise - // use the address - if len(s.opts.Advertise) > 0 { - host, port, err = net.SplitHostPort(s.opts.Advertise) - if err != nil { - logger.Fatal(err) - } - } - - addr, err := maddr.Extract(host) - if err != nil { - logger.Fatal(err) - } - - if strings.Count(addr, ":") > 0 { - addr = "[" + addr + "]" - } - - return ®istry.Service{ - Name: s.opts.Name, - Version: s.opts.Version, - Nodes: []*registry.Node{{ - Id: s.opts.Id, - Address: fmt.Sprintf("%s:%s", addr, port), - Metadata: s.opts.Metadata, - }}, - } -} - -func (s *service) run(exit chan bool) { - s.RLock() - if s.opts.RegisterInterval <= time.Duration(0) { - s.RUnlock() - return - } - - t := time.NewTicker(s.opts.RegisterInterval) - s.RUnlock() - - for { - select { - case <-t.C: - s.register() - case <-exit: - t.Stop() - return - } - } -} - -func (s *service) register() error { - s.Lock() - defer s.Unlock() - - if s.srv == nil { - return nil - } - // default to service registry - r := s.opts.Registry - - // service node need modify, node address maybe changed - srv := s.genSrv() - srv.Endpoints = s.srv.Endpoints - s.srv = srv - - // use RegisterCheck func before register - if err := s.opts.RegisterCheck(s.opts.Context); err != nil { - if logger.V(logger.ErrorLevel, logger.DefaultLogger) { - logger.Errorf("Server %s-%s register check error: %s", s.opts.Name, s.opts.Id, err) - } - return err - } - - var regErr error - - // register options - rOpts := []registry.RegisterOption{ - registry.RegisterTTL(s.opts.RegisterTTL), - } - - // try three times if necessary - for i := 0; i < 3; i++ { - // attempt to register - if err := r.Register(s.srv, rOpts...); err != nil { - // set the error - regErr = err - // backoff then retry - time.Sleep(backoff.Do(i + 1)) - continue - } - // success so nil error - regErr = nil - break - } - - return regErr -} - -func (s *service) deregister() error { - s.Lock() - defer s.Unlock() - - if s.srv == nil { - return nil - } - // default to service registry - r := s.opts.Registry - - return r.Deregister(s.srv) -} - -func (s *service) start() error { - s.Lock() - defer s.Unlock() - - if s.running { - return nil - } - - for _, fn := range s.opts.BeforeStart { - if err := fn(); err != nil { - return err - } - } - - l, err := s.listen("tcp", s.opts.Address) - if err != nil { - return err - } - - s.opts.Address = l.Addr().String() - srv := s.genSrv() - srv.Endpoints = s.srv.Endpoints - s.srv = srv - - var h http.Handler - - if s.opts.Handler != nil { - h = s.opts.Handler - } else { - h = s.mux - var r sync.Once - - // register the html dir - r.Do(func() { - // static dir - static := s.opts.StaticDir - if s.opts.StaticDir[0] != '/' { - dir, _ := os.Getwd() - static = filepath.Join(dir, static) - } - - // set static if no / handler is registered - if s.static { - _, err := os.Stat(static) - if err == nil { - if logger.V(logger.InfoLevel, logger.DefaultLogger) { - logger.Infof("Enabling static file serving from %s", static) - } - s.mux.Handle("/", http.FileServer(http.Dir(static))) - } - } - }) - } - - var httpSrv *http.Server - if s.opts.Server != nil { - httpSrv = s.opts.Server - } else { - httpSrv = &http.Server{} - } - - httpSrv.Handler = h - - go httpSrv.Serve(l) - - for _, fn := range s.opts.AfterStart { - if err := fn(); err != nil { - return err - } - } - - s.exit = make(chan chan error, 1) - s.running = true - - go func() { - ch := <-s.exit - ch <- l.Close() - }() - - if logger.V(logger.InfoLevel, logger.DefaultLogger) { - logger.Infof("Listening on %v", l.Addr().String()) - } - return nil -} - -func (s *service) stop() error { - s.Lock() - defer s.Unlock() - - if !s.running { - return nil - } - - for _, fn := range s.opts.BeforeStop { - if err := fn(); err != nil { - return err - } - } - - ch := make(chan error, 1) - s.exit <- ch - s.running = false - - if logger.V(logger.InfoLevel, logger.DefaultLogger) { - logger.Info("Stopping") - } - - for _, fn := range s.opts.AfterStop { - if err := fn(); err != nil { - if chErr := <-ch; chErr != nil { - return chErr - } - return err - } - } - - return <-ch -} - -func (s *service) Handle(pattern string, handler http.Handler) { - var seen bool - s.RLock() - for _, ep := range s.srv.Endpoints { - if ep.Name == pattern { - seen = true - break - } - } - s.RUnlock() - - // if its unseen then add an endpoint - if !seen { - s.Lock() - s.srv.Endpoints = append(s.srv.Endpoints, ®istry.Endpoint{ - Name: pattern, - }) - s.Unlock() - } - - // disable static serving - if pattern == "/" { - s.Lock() - s.static = false - s.Unlock() - } - - // register the handler - s.mux.Handle(pattern, handler) -} - -func (s *service) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) { - - var seen bool - s.RLock() - for _, ep := range s.srv.Endpoints { - if ep.Name == pattern { - seen = true - break - } - } - s.RUnlock() - - if !seen { - s.Lock() - s.srv.Endpoints = append(s.srv.Endpoints, ®istry.Endpoint{ - Name: pattern, - }) - s.Unlock() - } - - // disable static serving - if pattern == "/" { - s.Lock() - s.static = false - s.Unlock() - } - - s.mux.HandleFunc(pattern, handler) -} - -func (s *service) Init(opts ...Option) error { - s.Lock() - - for _, o := range opts { - o(&s.opts) - } - - srv := s.genSrv() - srv.Endpoints = s.srv.Endpoints - s.srv = srv - s.Unlock() - - return nil -} - -func (s *service) Run() error { - if err := s.start(); err != nil { - return err - } - - if err := s.register(); err != nil { - return err - } - - // start reg loop - ex := make(chan bool) - go s.run(ex) - - ch := make(chan os.Signal, 1) - if s.opts.Signal { - signal.Notify(ch, signalutil.Shutdown()...) - } - - select { - // wait on kill signal - case sig := <-ch: - if logger.V(logger.InfoLevel, logger.DefaultLogger) { - logger.Infof("Received signal %s", sig) - } - // wait on context cancel - case <-s.opts.Context.Done(): - if logger.V(logger.InfoLevel, logger.DefaultLogger) { - logger.Info("Received context shutdown") - } - } - - // exit reg loop - close(ex) - - if err := s.deregister(); err != nil { - return err - } - - return s.stop() -} - -// Options returns the options for the given service -func (s *service) Options() Options { - return s.opts -} - -func (s *service) listen(network, addr string) (net.Listener, error) { - var l net.Listener - var err error - - // TODO: support use of listen options - if s.opts.Secure || s.opts.TLSConfig != nil { - config := s.opts.TLSConfig - - fn := func(addr string) (net.Listener, error) { - if config == nil { - hosts := []string{addr} - - // check if its a valid host:port - if host, _, err := net.SplitHostPort(addr); err == nil { - if len(host) == 0 { - hosts = maddr.IPs() - } else { - hosts = []string{host} - } - } - - // generate a certificate - cert, err := mls.Certificate(hosts...) - if err != nil { - return nil, err - } - config = &tls.Config{Certificates: []tls.Certificate{cert}} - } - return tls.Listen(network, addr, config) - } - - l, err = mnet.Listen(addr, fn) - } else { - fn := func(addr string) (net.Listener, error) { - return net.Listen(network, addr) - } - - l, err = mnet.Listen(addr, fn) - } - - if err != nil { - return nil, err - } - - return l, nil -} diff --git a/web/service_test.go b/web/service_test.go deleted file mode 100644 index bd865774..00000000 --- a/web/service_test.go +++ /dev/null @@ -1,300 +0,0 @@ -package web - -import ( - "crypto/tls" - "fmt" - "io/ioutil" - "net/http" - "os" - "os/signal" - "syscall" - "testing" - "time" - - "github.com/micro/go-micro/v3/registry" - "github.com/micro/go-micro/v3/registry/memory" -) - -func TestService(t *testing.T) { - var ( - beforeStartCalled bool - afterStartCalled bool - beforeStopCalled bool - afterStopCalled bool - str = `

Hello World

` - fn = func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, str) } - reg = memory.NewRegistry() - ) - - beforeStart := func() error { - beforeStartCalled = true - return nil - } - - afterStart := func() error { - afterStartCalled = true - return nil - } - - beforeStop := func() error { - beforeStopCalled = true - return nil - } - - afterStop := func() error { - afterStopCalled = true - return nil - } - - service := NewService( - Name("go.micro.web.test"), - Registry(reg), - BeforeStart(beforeStart), - AfterStart(afterStart), - BeforeStop(beforeStop), - AfterStop(afterStop), - ) - - service.HandleFunc("/", fn) - - errCh := make(chan error, 1) - go func() { - errCh <- service.Run() - close(errCh) - }() - - var s []*registry.Service - - eventually(func() bool { - var err error - s, err = reg.GetService("go.micro.web.test") - return err == nil - }, t.Fatal) - - if have, want := len(s), 1; have != want { - t.Fatalf("Expected %d but got %d services", want, have) - } - - rsp, err := http.Get(fmt.Sprintf("http://%s", s[0].Nodes[0].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) != str { - t.Errorf("Expected %s got %s", str, string(b)) - } - - callbackTests := []struct { - subject string - have interface{} - }{ - {"beforeStartCalled", beforeStartCalled}, - {"afterStartCalled", afterStartCalled}, - } - - for _, tt := range callbackTests { - if tt.have != true { - t.Errorf("unexpected %s: want true, have false", tt.subject) - } - } - - select { - case err := <-errCh: - if err != nil { - t.Fatalf("service.Run():%v", err) - } - case <-time.After(time.Duration(time.Second)): - if len(os.Getenv("IN_TRAVIS_CI")) == 0 { - t.Logf("service.Run() survived a client request without an error") - } - } - - ch := make(chan os.Signal, 1) - signal.Notify(ch, syscall.SIGTERM) - p, _ := os.FindProcess(os.Getpid()) - p.Signal(syscall.SIGTERM) - - <-ch - - select { - case err := <-errCh: - if err != nil { - t.Fatalf("service.Run():%v", err) - } else { - if len(os.Getenv("IN_TRAVIS_CI")) == 0 { - t.Log("service.Run() nil return on syscall.SIGTERM") - } - } - case <-time.After(time.Duration(time.Second)): - if len(os.Getenv("IN_TRAVIS_CI")) == 0 { - t.Logf("service.Run() survived a client request without an error") - } - } - - eventually(func() bool { - _, err := reg.GetService("go.micro.web.test") - return err == registry.ErrNotFound - }, t.Error) - - callbackTests = []struct { - subject string - have interface{} - }{ - {"beforeStopCalled", beforeStopCalled}, - {"afterStopCalled", afterStopCalled}, - } - - for _, tt := range callbackTests { - if tt.have != true { - t.Errorf("unexpected %s: want true, have false", tt.subject) - } - } - -} - -func TestOptions(t *testing.T) { - var ( - name = "service-name" - id = "service-id" - version = "service-version" - address = "service-addr:8080" - advertise = "service-adv:8080" - reg = memory.NewRegistry() - registerTTL = 123 * time.Second - registerInterval = 456 * time.Second - handler = http.NewServeMux() - metadata = map[string]string{"key": "val"} - secure = true - ) - - service := NewService( - Name(name), - Id(id), - Version(version), - Address(address), - Advertise(advertise), - Registry(reg), - RegisterTTL(registerTTL), - RegisterInterval(registerInterval), - Handler(handler), - Metadata(metadata), - Secure(secure), - ) - - opts := service.Options() - - tests := []struct { - subject string - want interface{} - have interface{} - }{ - {"name", name, opts.Name}, - {"version", version, opts.Version}, - {"id", id, opts.Id}, - {"address", address, opts.Address}, - {"advertise", advertise, opts.Advertise}, - {"registry", reg, opts.Registry}, - {"registerTTL", registerTTL, opts.RegisterTTL}, - {"registerInterval", registerInterval, opts.RegisterInterval}, - {"handler", handler, opts.Handler}, - {"metadata", metadata["key"], opts.Metadata["key"]}, - {"secure", secure, opts.Secure}, - } - - for _, tc := range tests { - if tc.want != tc.have { - t.Errorf("unexpected %s: want %v, have %v", tc.subject, tc.want, tc.have) - } - } -} - -func eventually(pass func() bool, fail func(...interface{})) { - tick := time.NewTicker(10 * time.Millisecond) - defer tick.Stop() - - timeout := time.After(time.Second) - - for { - select { - case <-timeout: - fail("timed out") - return - case <-tick.C: - if pass() { - return - } - } - } -} - -func TestTLS(t *testing.T) { - var ( - str = `

Hello World

` - fn = func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, str) } - secure = true - reg = memory.NewRegistry() - ) - - service := NewService( - Name("go.micro.web.test"), - Secure(secure), - Registry(reg), - ) - - service.HandleFunc("/", fn) - - errCh := make(chan error, 1) - go func() { - errCh <- service.Run() - close(errCh) - }() - - var s []*registry.Service - - eventually(func() bool { - var err error - s, err = reg.GetService("go.micro.web.test") - return err == nil - }, t.Fatal) - - if have, want := len(s), 1; have != want { - t.Fatalf("Expected %d but got %d services", want, have) - } - - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - client := &http.Client{Transport: tr} - rsp, err := client.Get(fmt.Sprintf("https://%s", s[0].Nodes[0].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) != str { - t.Errorf("Expected %s got %s", str, string(b)) - } - - select { - case err := <-errCh: - if err != nil { - t.Fatalf("service.Run():%v", err) - } - case <-time.After(time.Duration(time.Second)): - if len(os.Getenv("IN_TRAVIS_CI")) == 0 { - t.Logf("service.Run() survived a client request without an error") - } - } - -} diff --git a/web/web.go b/web/web.go deleted file mode 100644 index c59ba1c0..00000000 --- a/web/web.go +++ /dev/null @@ -1,44 +0,0 @@ -// Package web provides web based micro services -package web - -import ( - "context" - "net/http" - "time" - - "github.com/google/uuid" -) - -// Service is a web service with service discovery built in -type Service interface { - Init(opts ...Option) error - Options() Options - Handle(pattern string, handler http.Handler) - HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) - Run() error -} - -//Option for web -type Option func(o *Options) - -//Web basic Defaults -var ( - // For serving - DefaultName = "go-web" - DefaultVersion = "latest" - DefaultId = uuid.New().String() - DefaultAddress = ":0" - - // for registration - DefaultRegisterTTL = time.Second * 90 - DefaultRegisterInterval = time.Second * 30 - - // static directory - DefaultStaticDir = "html" - DefaultRegisterCheck = func(context.Context) error { return nil } -) - -// NewService returns a new web.Service -func NewService(opts ...Option) Service { - return newService(opts...) -} diff --git a/web/web_test.go b/web/web_test.go deleted file mode 100644 index 6647aead..00000000 --- a/web/web_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package web_test - -import ( - "context" - "fmt" - "sync" - "testing" - "time" - - "github.com/micro/go-micro/v3/logger" - "github.com/micro/go-micro/v3/web" -) - -func TestWeb(t *testing.T) { - for i := 0; i < 3; i++ { - fmt.Println("Test nr", i) - testFunc() - } -} - -func testFunc() { - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*250) - defer cancel() - - w := web.NewService( - web.Context(ctx), - web.HandleSignal(false), - ) - //s.Init() - //w.Init() - - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - err := w.Run() - if err != nil { - logger.Errorf("web run error: %v", err) - } - }() - - wg.Wait() -}