go micro web

examples/web 有一個web的例子,這裏比較簡單html

service.HandleFunc("/", helloWorldHandler)node

這一行指定處理程序比較簡單,第2個參數定義了一個函數,只要知足條件就行web

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

實際項目中不太可能只用go micro, 從0開始手擼全部其餘輪子,那麼可不能夠在go micro中引入經常使用的框架呢?api

固然能夠,來看一個引入gin的例子examples/greeter/api/gin/gin.goapp

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.Handlertcp

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等例子oop

下面看看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相似,大同小異。

go micro 分析系列文章
go micro server 啓動分析
go micro client
go micro broker
go micro cmd
go micro config
go micro store
go micro registry
go micro router
go micro runtime
go micro transport
go micro web
go micro registry 插件consul
go micro plugin
go micro jwt 網關鑑權
go micro 鏈路追蹤
go micro 熔斷與限流
go micro wrapper 中間件
go micro metrics 接入Prometheus、Grafana

相關文章
相關標籤/搜索