乐趣区

关于micro:go-micro-web

examples/web 有一个 web 的例子,这里比较简单

service.HandleFunc("/", helloWorldHandler)

这一行指定处理程序比较简单,第 2 个参数定义了一个函数,只有满足条件就行

handler func(http.ResponseWriter, *http.Request)

理论我的项目中不太可能只用 go micro, 从 0 开始手撸所有其余轮子,那么可不可以在 go micro 中引入罕用的框架呢?

当然能够,来看一个引入 gin 的例子 examples/greeter/api/gin/gin.go

func main() {
    // Create service
    service := web.NewService(web.Name("go.micro.api.greeter"),
    )

    service.Init()

    // setup Greeter Server Client
    cl = hello.NewSayService("go.micro.srv.greeter", client.DefaultClient)

    // Create RESTful handler (using Gin)
    say := new(Say)
    router := gin.Default()
    router.GET("/greeter", say.Anything)
    router.GET("/greeter/:name", say.Hello)

    // Register Handler
    service.Handle("/", router)

    // Run server
    if err := service.Run(); err != nil {log.Fatal(err)
    }
}

要害是service.Handle("/", router)

这个 router 是 gin.Engine, service.Handle()的第二个参数是handler http.Handler

type Handler interface {ServeHTTP(ResponseWriter, \*Request)
}

也就是 gin.Engine 中只有实现了 ServeHTTP()就能够满足条件,来看下 gin 的 ServeHTTP()

// ServeHTTP conforms to the http.Handler interface.
func (engine \*Engine) ServeHTTP(w http.ResponseWriter, req \*http.Request) {c := engine.pool.Get().(\*Context)
    c.writermem.reset(w)
    c.Request = req
    c.reset()
    engine.handleHTTPRequest(c)
    engine.pool.Put(c)
}

这样就能够应用 gin 框架实现业务代码了,其余框架都相似,examples/greeter/api 目录下有 beego、graphql、rest、rpc 等例子

上面看看 web 的整个启动流程

以 examples/web 为例

func main() {
    service := web.NewService(web.Name("go.micro.web.greeter"),
    )

    service.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        if r.Method == "POST" {r.ParseForm()

            name := r.Form.Get("name")
            if len(name) == 0 {name = "World"}

            cl := hello.NewSayService("go.micro.srv.greeter", client.DefaultClient)
            rsp, err := cl.Hello(context.Background(), &hello.Request{Name: name,})

            if err != nil {http.Error(w, err.Error(), 500)
                return
            }

            w.Write([]byte(`<html><body><h1>` + rsp.Msg + `</h1></body></html>`))
            return
        }

        fmt.Fprint(w, `<html><body><h1>Enter Name<h1><form method=post><input name=name type=text /></form></body></html>`)
    })

    if err := service.Init(); err != nil {log.Fatal(err)
    }

    if err := service.Run(); err != nil {log.Fatal(err)
    }
}

代码构造和一般 service 一样,只是 micro 变成了 web
先看 web.NewService()

// NewService returns a new web.Service
func NewService(opts ...Option) Service {return newService(opts...)
}

func newService(opts ...Option) Service {options := newOptions(opts...)
    s := &service{
        opts:   options,
        mux:    http.NewServeMux(),
        static: true,
    }
    s.srv = s.genSrv()
    return s
}

func newOptions(opts ...Option) Options {
    opt := Options{
        Name:             DefaultName,
        Version:          DefaultVersion,
        Id:               DefaultId,
        Address:          DefaultAddress,
        RegisterTTL:      DefaultRegisterTTL,
        RegisterInterval: DefaultRegisterInterval,
        StaticDir:        DefaultStaticDir,
        Service:          micro.NewService(),
        Context:          context.TODO(),
        Signal:           true,
    }

    for _, o := range opts {o(&opt)
    }

    if opt.RegisterCheck == nil {opt.RegisterCheck = DefaultRegisterCheck}

    return opt
}

做了以下事件

  1. 初始化并设置 options,其中 Options.Service 是 micro
  2. 初始化 web.service{}, 其中 mux 初始化了 http.ServeMux
  3. s.genSrv()生成 registry.Service{}

    1. 获取 ip:port 并验证
    2. 返回 registry.Service{}
  4. 返回 web.Service

再来看service.Init()

func (s *service) Init(opts ...Option) error {s.Lock()

    for _, o := range opts {o(&s.opts)
    }

    serviceOpts := []micro.Option{}

    if len(s.opts.Flags) > 0 {serviceOpts = append(serviceOpts, micro.Flags(s.opts.Flags...))
    }

    if s.opts.Registry != nil {serviceOpts = append(serviceOpts, micro.Registry(s.opts.Registry))
    }

    s.Unlock()

    serviceOpts = append(serviceOpts, micro.Action(func(ctx *cli.Context) error {s.Lock()
        defer s.Unlock()

        if ttl := ctx.Int("register_ttl"); ttl > 0 {s.opts.RegisterTTL = time.Duration(ttl) * time.Second
        }

        if interval := ctx.Int("register_interval"); interval > 0 {s.opts.RegisterInterval = time.Duration(interval) * time.Second
        }

        if name := ctx.String("server_name"); len(name) > 0 {s.opts.Name = name}

        if ver := ctx.String("server_version"); len(ver) > 0 {s.opts.Version = ver}

        if id := ctx.String("server_id"); len(id) > 0 {s.opts.Id = id}

        if addr := ctx.String("server_address"); len(addr) > 0 {s.opts.Address = addr}

        if adv := ctx.String("server_advertise"); len(adv) > 0 {s.opts.Advertise = adv}

        if s.opts.Action != nil {s.opts.Action(ctx)
        }

        return nil
    }))

    s.RLock()
    // pass in own name and version
    if s.opts.Service.Name() == "" {serviceOpts = append(serviceOpts, micro.Name(s.opts.Name))
    }
    serviceOpts = append(serviceOpts, micro.Version(s.opts.Version))
    s.RUnlock()
    fmt.Println(s.opts.Service)
    s.opts.Service.Init(serviceOpts...)

    s.Lock()
    srv := s.genSrv()
    srv.Endpoints = s.srv.Endpoints
    s.srv = srv
    s.Unlock()

    return nil
}

做了以下事件

  1. 设置 options
  2. 整顿 serviceOpts
  3. 调用s.opts.Service.Init(serviceOpts...),就是调用 micro.Init, 细节请见【micro server】
  4. 获取 registry.Service{}, 复制给 web.srv, 这一步在 newService()中曾经做了,这里可能是版本还在迭代中,反复了

最初看看service.Run()

func (s *service) Run() error {
    // generate an auth account
    srvID := s.opts.Service.Server().Options().Id
    srvName := s.Options().Name
    if err := authutil.Generate(srvID, srvName, s.opts.Service.Options().Auth); err != nil {return err}

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

做了以下事件

  1. 拿到 srvID,srvName, 生成 auth account
  2. 调用s.start()

    1. 顺次执行 s.opts.BeforeStart()
    2. s.listen(“tcp”, s.opts.Address)监听端口
    3. 有自定义的 s.opts.Handler 就用自定义的,没有就设置下 html 目录
    4. go httpSrv.Serve(l),开始 web 服务
    5. 顺次执行 s.opts.AfterStart()
    6. 开协程监听退出信号, 收到信号调用l.Close()
  3. 调用s.register()

    1. 从 micro 拿到 Registry
    2. 从新取 registry.Service{}, 每次都从新取啊,正文说是在流程中 node 地址可能扭转
    3. s.opts.RegisterCheck(), 注册逻辑和 server 一样,详情见【micro server】
  4. 定义退出信号 chan,开协程 go s.run(ex),监听 Shutdown 信号、ctx.Done() 信号,敞开 chan

    1. 定时向注册核心注册,收到退出信号后进行,并调用 t.Stop()
  5. 收到退出信号后,调用s.deregister()s.stop()

    1. s.deregister() -> r.Deregister(s.srv),调用注册核心的 Deregister()办法
    2. s.stop()

      1. 顺次调用 s.opts.BeforeStop()
      2. 告诉退出信号,s.running = false
      3. 顺次调用 s.opts.AfterStop()

流程与 server 相似,大同小异。

退出移动版