百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程文章 > 正文

混合云资产管理项目(七)

qiyuwang 2024-11-22 19:43 13 浏览 0 评论

本文对resource和host模块添加restful api,将接口暴露出去供前端或其他模块调用。api的暴露使用go-restful框架

restful框架

要想理解RESTful,就需要先明白REST。REST是 Roy T. Fielding 在其2000年的博士论文中提出的,是REpresentational State Transfer 词组的缩写,可以翻译为“表现层状态转移”,其实这个词组前面省略了个主语--“Resource”,加上主语后就是“资源表现层状态转移”。每个词都能看懂,连在一起不知道什么意思了有木有?

  • Resource(资源)
  • 所谓资源,就是互联网上的一个实体。URI(Uniform Resource Identifier)的全称就是统一资源标识符,和我们这里的资源一个意思。一个资源可以是一段文字、一张图片、一段音频、一个服务。
  • 表现层(Representation)
  • "资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。比如一篇文章,可以使用XML、JSON、HTML的形式呈现出来。
  • 状态转移(State Transfer)
  • 访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。互联网通信协议HTTP协议,是一个无状态协议,这意味着所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。

一个 RESTful API 框架需要什么?

从应用程序开发的角度来看,RESTful API 的本质是一个 Web Application,而 RESTful API 框架就是实现这个 Web Application 所封装的一些列工具库,使开发者可以忽略底层实现的复杂度,专注以自身 Application 的逻辑设计。

一个 RESTful API 框架应该具备以下几个元素:

Resources:资源的定义,即 HTTP URI(或称之为 HTTP URL Path)的定义。RESTful API 的设计围绕着 Resource 进行建模。

Handlers:资源处理器,是资源业务逻辑处理的具体实现。

Request Routers:资源请求路由器,完成 HTTP URIs、HTTP Request Methods 和 Handlers 三者之间的映射与路由。

Request Verification Schemas:HTTP Request Body 校验器,验证请求实体的合法性。

Response View Builder:HTTP Response Body 生成器,生成合法的响应实体。

Controllers:资源表现层状态转移控制器,每个 Resource 都有着各自的 Controller,将 Resource 自身及其所拥有的 Handlers、Request Verification Schemas 以及 Response View Builder 进行封装,配合 Request Routers 完成 RESTful 请求的处理即响应。

go-restful

go-restful 是一个 Golang 第三方库,是一个轻量的 RESTful API 框架,基于 Golang Build-in 的 http/net 库。适用于构建灵活多变的 Web Application,Kubernetes 的 ApiServer 也使用了 go-restful。

Github:https://github.com/emicklei/go-restful

Doc:https://godoc.org/github.com/emicklei/go-restful

go-restful 具有以下特性:

支持可配置的请求路由,默认使用 CurlyRouter 快速路由算法,也支持 RouterJSR311。 支持在 URL path 上定义正则表达式,例如:/static/{subpath:*}。 提供 Request API 用于从 JSON、XML 读取路径参数、查询参数、头部参数,并转换为 Struct。 提供 Response API 用于将 Struct 写入到 JSON、XML 以及 Header。 支持在服务级、或路由级对请求、响应流进行过滤和拦截。 支持使用过滤器自动响应 OPTIONS 请求和 CORS(跨域)请求。 支持使用 RecoverHandler 自定义处理 HTTP 500 错误。 支持使用 ServiceErrorHandler 自定义处理路由错误产生 HTTP 404/405/406/415 等错误。 支持对请求、响应的有效负载进行编码(例如:gzip、deflate)。 支持使用 CompressorProvider 注册自定义的 gzip、deflate 的读入器和输出器。 支持使用 EntityReaderWriter 注册的自定义编码实现。 支持 Swagger UI 编写的 API 文档。 支持可配置的日志跟踪。

go-restful定义了三个重要的数据结构:

  • Router:表示一条路由,包含url、回调处理函数
  • Webservice:表示一个服务
  • Container:表示一个服务器

Route

Route 表示一条请求路由记录,即:Resource 的 URL Path(URI),从编程的角度可细分为 RootPath 和 SubPath。Route 包含了 Resource 的 URL Path、HTTP Method、Handler 三者之间的组合映射关系。go-restful 内置的 RouteSelector(请求路由分发器)根据 Route 将客户端发出的 HTTP 请求路由到相应的 Handler 进行处理。

go-restful 支持两种路由分发器:快速路由 CurlyRouter 和 RouterJSR311。实际上,CurlyRoute 也是基于 RouterJSR311 的,相比 RouterJSR11,还支持了正则表达式和动态参数,也更加轻量级,Kubernetes ApiServer 中使用的就是这种路由。

CurlyRouter 的元素包括:请求路径(URL Path),请求参数(Parameter),输入、输出类型(Writes、Reads Model),处理函数(Handler),响应内容类型(Accept)等。

WebService

一个 WebService 由若干个 Routes 组成,并且 WebService 内的 Routes 拥有同一个 RootPath、输入输出格式、基本一致的请求数据类型等等一系列的通用属性。通常的,我们会根据需要将一组相关性非常强的 API 封装成为一个 WebServiice,继而将 Web Application 所拥有的全部 APIs 划分若干个 Group。

所以,WebService 至少会有一个 Root Path,通过 ws.Path() 方法设置,例如:/user_group,作为 Group 的 “根”。Group 下属的 APIs 都是 RootRoute(RootPath)下属的 SubRoute(SubPath)。

每个 Group 就是提供一项服务的 API 集合,每个 Group 会维护一个 Version。Group 的抽象是为了能够安全隔离的对各项服务进行敏捷迭代,当我们对一项服务进行升级时,只需要通过对特定版本号的更新来升级相关的 APIs,而不会影响到整个 Web Server。视实际情况而定,可能是若干个 APIs 分为一个 Group,也有可能一个 API 就是一个 Group。

Container

Container 表示一个 Web Server(服务器),由多个 WebServices 组成,此外还包含了若干个 Filters(过滤器)、一个 http.ServeMux 多路复用器以及一个 dispatch。go-restful 如何在从 Container 开始将路由分发给各个 WebService,再由 WebService 分发给具体的 Handler 函数,这些都在 dispatch 中实现。

三者的关系如下:

  • go-restful支持多个container,一个container相当于一个http server,不同的container监控不同的地址和端口
  • 每个container可以包含多个webservice,相当于一组不同服务的分类
  • 每个webservice包含多个Router(路由),Router根据http请求的URL路由到对应的处理函数(Handler Func)

实现http的协议

protocol/http.go

// NewHTTPService 构建函数
func NewHTTPService() *HTTPService {

  r := restful.DefaultContainer
  // Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
  // You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
  // Open http://localhost:8080/apidocs/?url=http://localhost:8080/apidocs.json
  // http.Handle("/apidocs/", http.StripPrefix("/apidocs/", http.FileServer(http.Dir("/Users/emicklei/Projects/swagger-ui/dist"))))

  // Optionally, you may need to enable CORS for the UI to work.
  cors := restful.CrossOriginResourceSharing{
    AllowedHeaders: []string{"*"},
    AllowedMethods: []string{"*"},
    CookiesAllowed: false,
    Container:      r}
  r.Filter(cors.Filter)


  server := &http.Server{
    ReadHeaderTimeout: 60 * time.Second,
    ReadTimeout:       60 * time.Second,
    WriteTimeout:      60 * time.Second,
    IdleTimeout:       60 * time.Second,
    MaxHeaderBytes:    1 << 20, // 1M
    Addr:              conf.C().App.HTTP.Addr(),
    Handler:           r,
  }

  return &HTTPService{
    r:      r,
    server: server,
    l:      zap.L().Named("HTTP Service"),
    c:      conf.C(),
    mc:     rpc.C(),
  }
}

cmd/start.go

// startCmd represents the start command
var serviceCmd = &cobra.Command{
  Use:   "start",
  Short: "mycmdb API服务",
  Long:  "mycmdb API服务",
  RunE: func(cmd *cobra.Command, args []string) error {
    // 初始化全局变量
    if err := loadGlobalConfig(""); err != nil {
      return err
    }

    // 初始化全局日志配置
    if err := loadGlobalLogger(); err != nil {
      return err
    }

    // 初始化全局app
    if err := app.InitAllApp(); err != nil {
      return err
    }


    rpc.SetGlobal(mc)

    conf := conf.C()
    // 启动服务
    ch := make(chan os.Signal, 1)
    defer close(ch)
    signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP, syscall.SIGQUIT)

    // 初始化服务
    svr, err := newService(conf)
    if err != nil {
      return err
    }

    // 等待信号处理
    go svr.waitSign(ch)

    // 启动服务
    if err := svr.start(); err != nil {
      if !strings.Contains(err.Error(), "http: Server closed") {
        return err
      }
    }

    return nil
  },
}

func newService(cnf *conf.Config) (*service, error) {
  http := protocol.NewHTTPService()
  grpc := protocol.NewGRPCService()
  svr := &service{
    http: http,
    grpc: grpc,
    log:  zap.L().Named("CLI"),
  }

  return svr, nil
}

type service struct {
  http *protocol.HTTPService
  grpc *protocol.GRPCService

  log logger.Logger
}

func (s *service) start() error {
  s.log.Infof("loaded grpc app: %s", app.LoadedGrpcApp())
  s.log.Infof("loaded http app: %s", app.LoadedRESTfulApp())

  s.log.Infof("loaded internal app: %s", app.LoadedInternalApp())

  go s.grpc.Start()
  return s.http.Start()
}

// config 为全局变量, 只需要load 即可全局可用户
func loadGlobalConfig(string) error {
  //// 配置加载
  //switch configType {
  //case "file":
  //  err := conf.LoadConfigFromToml(confFile)
  //  if err != nil {
  //    return err
  //  }
  //case "env":
  //  err := conf.LoadConfigFromEnv()
  //  if err != nil {
  //    return err
  //  }
  //default:
  //  return errors.New("unknown config type")
  //}
  err := conf.LoadConfigFromToml("etc/config.toml")
  if err != nil {
    return err
  }

  return nil
}

// log 为全局变量, 只需要load 即可全局可用户, 依赖全局配置先初始化
func loadGlobalLogger() error {
  var (
    logInitMsg string
    level      zap.Level
  )
  lc := conf.C().Log
  lv, err := zap.NewLevel(lc.Level)
  if err != nil {
    logInitMsg = fmt.Sprintf("%s, use default level INFO", err)
    level = zap.InfoLevel
  } else {
    level = lv
    logInitMsg = fmt.Sprintf("log level: %s", lv)
  }
  zapConfig := zap.DefaultConfig()
  zapConfig.Level = level
  switch lc.To {
  case conf.ToStdout:
    zapConfig.ToStderr = true
    zapConfig.ToFiles = false
  case conf.ToFile:
    zapConfig.Files.Name = "api.log"
    zapConfig.Files.Path = lc.PathDir
  }
  switch lc.Format {
  case conf.JSONFormat:
    zapConfig.JSON = true
  }
  if err := zap.Configure(zapConfig); err != nil {
    return err
  }
  zap.L().Named("INIT").Info(logInitMsg)
  return nil
}

protocol/http.go

加载完之后注册服务

// 注册功能列表
func (s *HTTPService) RegistryEndpoint(ctx context.Context) {
  epss := []*endpoint.Entry{}
  wss := s.r.RegisteredWebServices()
  for i := range wss {
    eps := tools.TransferRoutesToEndpints(wss[i].Routes())
    epss = append(epss, eps...)
  }

  // mcenter的客户端
  req := endpoint.NewRegistryRequest("0.0.1", epss)
  req.ClientId = s.mc.Config().ClientID
  req.ClientSecret = s.mc.Config().ClientSecret
  resp, err := s.mc.Endpoint().RegistryEndpoint(ctx, req)
  if err != nil {
    s.l.Errorf("registry endpoint error, %s", err)
  } else {
    s.l.Debugf("registy success, %s", resp.Message)
  }
}

protocol/http.go

在start中注册

// Start 启动服务
func (s *HTTPService) Start() error {
  // 装置子服务路由
  app.LoadRESTfulApp(s.PathPrefix(), s.r)

  s.RegistryEndpoint(context.Background())

  // API Doc
  config := restfulspec.Config{
    WebServices:                   restful.RegisteredWebServices(), // you control what services are visible
    APIPath:                       "/apidocs.json",
    PostBuildSwaggerObjectHandler: swagger.Docs}
  s.r.Add(restfulspec.NewOpenAPIService(config))
  s.l.Infof("Get the API using http://%s%s", s.c.App.HTTP.Addr(), config.APIPath)

  // 启动 HTTP服务
  s.l.Infof("HTTP服务启动成功, 监听地址: %s", s.server.Addr)
  if err := s.server.ListenAndServe(); err != nil {
    if err == http.ErrServerClosed {
      s.l.Info("service is stopped")
    }
    return fmt.Errorf("start service error, %s", err.Error())
  }
  return nil
}

resource的api接口

apps/resource/api/http.go

对外的restful api只给了一个查询接口

var (
  h = &handler{}
)

type handler struct {
  service resource.Service
  log     logger.Logger
}

func (h *handler) Config() error {
  h.log = zap.L().Named(resource.AppName)
  h.service = app.GetGrpcApp(resource.AppName).(resource.Service)
  return nil
}

func (h *handler) Name() string {
  return resource.AppName
}

func (h *handler) Version() string {
  return "v1"
}

func (h *handler) Registry(ws *restful.WebService) {
  tags := []string{"资源查询"}

  ws.Route(ws.GET("/").To(h.QueryResource).
    Metadata(label.Auth, true).
    Doc("create a book").
    Metadata(restfulspec.KeyOpenAPITags, tags))
}

func init() {
  app.RegistryRESTfulApp(h)
}

apps/resource/api/resource.go

go-restful实现http api的handler

func (h *handler) QueryResource(r *restful.Request, w *restful.Response) {
  req := resource.NewQueryResourceRequestFromHTTP(r.Request)
  set, err := h.service.QueryResource(r.Request.Context(), req)
  if err != nil {
    response.Failed(w.ResponseWriter, err)
    return
  }
  response.Success(w.ResponseWriter, set)
}

apps/resource/app.go

补充QueryResource的NewQueryResourceRequestFromHTTP

func NewQueryResourceRequestFromHTTP(r *http.Request) *QueryResourceRequest {
  req := NewQueryResourceRequest()
  qs := r.URL.Query()
  req.Domain = qs.Get("domain")
  req.Namespace = qs.Get("namespace")
  req.Env = qs.Get("env")
  req.Page = request.NewPageRequestFromHTTP(r)
  return req
}

apps/api.go

注册http服务模块
import (
  // 注册所有HTTP服务模块, 暴露给框架HTTP服务器加载
  _ "gitee.com/go-course/go8/projects/devcloud/cmdb/apps/resource/api"
)

测试该查询方法

make run运行

zengz@DESKTOP-Q3MJC54 MINGW64 /f/go/project/0-shizhanxiangmu/myCMDB/mycmdb (master)
$ make run                                                                         
2023-01-27 21:30:22     INFO    [INIT]  cmd/start.go:166        log level: debug
2023-01-27 21:30:22     INFO    [CLI]   cmd/start.go:100        loaded grpc app: [host resource secret]
2023-01-27 21:30:22     INFO    [CLI]   cmd/start.go:101        loaded http app: [secret host resource]    
2023-01-27 21:30:22     INFO    [CLI]   cmd/start.go:103        loaded internal app: [host resource secret]
2023-01-27 21:30:22     INFO    [GRPC Service]  protocol/grpc.go:54     GRPC 服务监听地址: 127.0.0.1:18060
2023-01-27 21:30:24     ERROR   [HTTP Service]  protocol/http.go:137    registry endpoint error, rpc error: code = Unavailable desc = connection error: desc = "transport: Error while d
ialing dial tcp 127.0.0.1:18050: connectex: No connection could be made because the target machine actively refused it."
2023-01-27 21:30:24     INFO    [HTTP Service]  protocol/http.go:97     Get the API using http://127.0.0.1:8060/apidocs.json
2023-01-27 21:30:24     INFO    [HTTP Service]  protocol/http.go:100    HTTP服务启动成功, 监听地址: 127.0.0.1:8060

查看api文档

访问路径查看数据

http接口验证通过



相关推荐

# 安装打开 ubuntu-22.04.3-LTS 报错 解决方案

#安装打开ubuntu-22.04.3-LTS报错解决方案WslRegisterDistributionfailedwitherror:0x800701bcError:0x80070...

利用阿里云镜像在ubuntu上安装Docker

简介:...

如何将Ubuntu Kylin(优麒麟)19.10系统升级到20.04版本

UbuntuKylin系统使用一段时间后,有新的版本发布,如何将现有的UbuntuKylin系统升级到最新版本?可以通过下面的方法进行升级。1.先查看相关的UbuntuKylin系统版本情况。使...

Ubuntu 16.10内部代号确认为Yakkety Yak

在正式宣布Ubuntu16.04LTS(XenialXerus)的当天,Canonical创始人MarkShuttleworth还非常开心的在个人微博上宣布Ubuntu下个版本16.10的内...

如何在win11的wsl上装ubuntu(怎么在windows上安装ubuntu)

在Windows11的WSL(WindowsSubsystemforLinux)上安装Ubuntu非常简单。以下是详细的步骤:---...

Win11学院:如何在Windows 11上使用WSL安装Ubuntu

IT之家2月18日消息,科技媒体pureinfotech昨日(2月17日)发布博文,介绍了3中简便的方法,让你轻松在Windows11系统中,使用WindowsSubs...

如何查看Linux的IP地址(如何查看Linux的ip地址)

本头条号每天坚持更新原创干货技术文章,欢迎关注本头条号"Linux学习教程",公众号名称“Linux入门学习教程"。...

怎么看电脑系统?(怎么看电脑系统配置)

要查看电脑的操作系统信息,可以按照以下步骤操作,根据不同的操作系统选择对应的方法:一、Windows系统通过系统属性查看右键点击桌面上的“此电脑”(或“我的电脑”)图标,选择“属性”。在打开的...

如何查询 Linux 内核版本?这些命令一定要会!

Linux内核是操作系统的核心,负责管理硬件资源、调度进程、处理系统调用等关键任务。不同的内核版本可能支持不同的硬件特性、提供新的功能,或者修复了已知的安全漏洞。以下是查询内核版本的几个常见场景:...

深度剖析:Linux下查看系统版本与CPU架构

在Linux系统管理、维护以及软件部署的过程中,精准掌握系统版本和CPU架构是极为关键的基础操作。这些信息不仅有助于我们深入了解系统特性、判断软件兼容性,还能为后续的软件安装、性能优化提供重要依据。接...

504 错误代码解析与应对策略(504错误咋解决)

在互联网的使用过程中,用户偶尔会遭遇各种错误提示,其中504错误代码是较为常见的一种。504错误并非意味着网站被屏蔽,它实际上是指服务器在规定时间内未能从上游服务器获取响应,专业术语称为“Ga...

猎聘APP和官网崩了?回应:正对部分职位整改,临时域名可登录

10月12日,有网友反映猎聘网无法打开,猎聘APP无法登录。截至10月14日,仍有网友不断向猎聘官方微博下反映该情况,而猎聘官方微博未发布相关情况说明,只是在微博内对反映该情况的用户进行回复,“抱歉,...

域名解析的原理是什么?域名解析的流程是怎样的?

域名解析是网站正常运行的关键因素,因此网站管理者了解域名解析的原理和流程对于做好域名管理、解决常见解析问题,保障网站的正常运转十分必要。那么域名解析的原理是什么?域名解析的流程是怎样的?接下来,中科三...

Linux无法解析域名的解决办法(linux 不能解析域名)

如果由于误操作,删除了系统原有的dhcp相关设置就无法正常解析域名。  此时,需要手动修改配置文件:  /etc/resolv.conf  将域名解析服务器手动添加到配置文件中  该文件是DNS域名解...

域名劫持是什么?(域名劫持是什么)

域名劫持是互联网攻击的一种方式,通过攻击域名解析服务器(DNS),或伪造域名解析服务器(DNS)的方法,把目标网站域名解析到错误的地址从而实现用户无法访问目标网站的目的。说的直白些,域名劫持,就是把互...

取消回复欢迎 发表评论: