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 = `