Remove go-micro/web

This commit is contained in:
Asim Aslam 2020-08-04 07:47:20 +01:00
parent 1439b101ec
commit 8e126e4fc1
5 changed files with 0 additions and 1081 deletions

View File

@ -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
}
}

View File

@ -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 &registry.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, &registry.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, &registry.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
}

View File

@ -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 = `<html><body><h1>Hello World</h1></body></html>`
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 = `<html><body><h1>Hello World</h1></body></html>`
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")
}
}
}

View File

@ -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...)
}

View File

@ -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()
}