RESTful API 的实现与调用(GO 版本)

摘要

在代码开源开放的时代下,跨语言,跨平台的接口交互也是其一种表征,本文基于 go 语言通过  restful.WebService 实现一个小项目,从而更好的理解 restfulApi 的实现原理,restfulapi 无论是 go ,python,java 等众多语言都有相关的实现和调用,无论在云计算,互联网移动应用都有直接的应用实现;因此掌握 restfulAPI 的应用对于较好的构建完成项目都是大有裨益的一项技能。

关键词: restfulAPI;go; python; 云原生;

RESTful 简介

OpenApi VS RESTful ?

提到 OpenAPI,就不得不提 RESTful API。简单来说,RESTful API 是指导设计 Web 应用 API 的规范,而 OpenAPI 是指导编写 Web 应用 API 文档的规范,是对 RESTful API 实现细节的描述。服务使用能够根据符合 OpenAPI 规范的应用服务API 文档理解调用服务的方式,并通过工具快速生成客户端代码以及交互式的 UI 界面。

What RESTful ?

RESTful 是一种网络应用程序的开发方式或设计风格,基于 HTTP 网络协议,可以使用 XML 或 JSON 格式定义,目前使用最多的是 JSON 格式,因为它相比 XML 体积更小,传输速度更快,承载内容更多。

How RESTful ?

RESTful  开发方式大多应用在前端和后端开发分离的环境中,前端和后端分离带来的最大好处就是项目耦合度得到降低,前端通过 RESTful  API 接口服务完成对数据的增删改查,也就是常说的对数据库的 CRUD

RESTful 框架

Python 中的  RESTful Web 框架

Django REST framework 是一个功能强大且灵活的 REST Web API 框架,包含 OAuth1a、OAuth2身份验证策略,支持 ORM 和非 ORM 数据源的序列化,使用基于函数的常规视图实现自定义你所需要的功能,有广泛的文档资料和社区支持。

Flask-RESTful 是一个非常轻量级,能够快速上手的 RESTful Web API 框架。可以快速搭建并运行完成一个  RESTful  API 服务。相对于 Django REST framework 是比较轻量级的 RESTful API 框架。

FastAPI 是一个高性能的异步Web框架,具有并发性能强、容错性能强,快速上手,自动生成交互式文档,堪称目前最快最高效的 Web 框架。

GO 中的  RESTful Web 框架

待补充ing

请求方法

备注: PUT方法主要是用来更新整个资源的,而PATCH方法主要是用来执行某项操作并更新资源的某些字段;

编码实践

第一步: 新建 main.go 文件
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/emicklei/go-restful"
)

// This example has the same service definition as restful-user-resource
// but uses a different router (CurlyRouter) that does not use regular expressions
//
// POST http://localhost:8080/users
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
//
// GET http://localhost:8080/users/1
//
// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa</Name></User>
//
// DELETE http://localhost:8080/users/1
//

type User struct {
	Id, Name string
}

type UserResource struct {
	// normally one would use DAO (data access object)
	users map[string]User
}

func (u UserResource) Register(container *restful.Container) {
	ws := new(restful.WebService)
	ws.
		Path("/users").
		Consumes(restful.MIME_XML, restful.MIME_JSON).
		Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well

	ws.Route(ws.GET("/{user-id}").To(u.findUser))
	ws.Route(ws.POST("").To(u.updateUser))
	ws.Route(ws.PUT("/{user-id}").To(u.createUser))
	ws.Route(ws.DELETE("/{user-id}").To(u.removeUser))

	container.Add(ws)
}

// GET http://localhost:8080/users/1
//
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
	id := request.PathParameter("user-id")
	usr, ok := u.users[id]
	if !ok {
		response.AddHeader("Content-Type", "text/plain")
		response.WriteErrorString(http.StatusNotFound, "User could not be found.")
	} else {
		response.WriteEntity(usr)
	}
}

// POST http://localhost:8080/users
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
//
func (u *UserResource) updateUser(request *restful.Request, response *restful.Response) {
	usr := new(User)
	err := request.ReadEntity(&usr)
	if err == nil {
		u.users[usr.Id] = *usr
		response.WriteEntity(usr)
	} else {
		response.AddHeader("Content-Type", "text/plain")
		response.WriteErrorString(http.StatusInternalServerError, err.Error())
	}
}

// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa</Name></User>
//
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
	usr := User{Id: request.PathParameter("user-id")}
	err := request.ReadEntity(&usr)
	if err == nil {
		u.users[usr.Id] = usr
		response.WriteHeaderAndEntity(http.StatusCreated, usr)
	} else {
		fmt.Println("err")
		response.AddHeader("Content-Type", "text/plain")
		response.WriteErrorString(http.StatusInternalServerError, err.Error())
	}
}

// DELETE http://localhost:8080/users/1
//
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
	id := request.PathParameter("user-id")
	delete(u.users, id)
}

func main() {
	wsContainer := restful.NewContainer()
	wsContainer.Router(restful.CurlyRouter{})
	u := UserResource{map[string]User{}}
	u.Register(wsContainer)

	log.Printf("start listening on localhost:8080")
	server := &http.Server{Addr: ":8080", Handler: wsContainer}
	log.Fatal(server.ListenAndServe())
}
第二步: 编译运行
# 下载依赖
go mod init
go mod tidy

# 本地运行
go run main.go
第三步: 请求测试
curl -X PUT -v -i http://10.188.136.34:8080/users/1  -H 'Content-type: application/json'  -H 'Accept: application/xml'  -d '{"Id": "1", "Name": "HHD"}'
curl -X POST -v -i http://10.188.136.34:8080/users  -H 'Content-type: application/json'  -H 'Accept: application/xml'  -d '{"Id": "1", "Name": "VPC123"}'
curl -X GET -v -i http://10.188.136.34:8080/users/1  -H 'Content-type: application/json'  -H 'Accept: application/xml' 
curl -X DELETE -v -i http://10.188.136.34:8080/users/1  -H 'Content-type: application/json'  -H 'Accept: application/xml' 

Swager-UI

a. 安装部署

Swagger 允许您描述 API 的结构,以便机器能够读取它们。Swagger 所有能力中最卓越的是 api 描述自身结构的能力。使用者可以自动构建漂亮的交互式 API 文档。还可以用多种语言为您的 API 自动生成服务端库,并探索其他可能性,比如自动化测试。Swagger 通过请求 API 返回包含整个 API 详细描述的 YAML 或 JSON 来实现这一点。这个文件本质上是您的API的资源列表,它遵循OpenAPI规范。该规范要求您包括以下信息:

您的API支持哪些操作?
您的API的参数是什么?它返回什么?
您的API需要一些授权吗?
甚至还有一些有趣的东西,比如术语、联系信息和使用API的许可。

随便一台机器运行 swagger 渲染器:

docker run -d -p 8082:8080 swaggerapi/swagger-ui

备注: 并不是一定本机运行,任意一台可以可以访问的位置运行即可,swagger-ui 和数据接口可以互访互通即可;

b. 服务测验

第一步: 编辑 main.go 文件

package main

import (
	"log"
	"net/http"

	"github.com/emicklei/go-restful"
	restfulspec "github.com/emicklei/go-restful-openapi"
	"github.com/go-openapi/spec"
)

type UserResource struct {
	// normally one would use DAO (data access object)
	users map[string]User
}

func (u UserResource) WebService() *restful.WebService {
	ws := new(restful.WebService)
	ws.
		Path("/users").
		Consumes(restful.MIME_XML, restful.MIME_JSON).
		Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well

	tags := []string{"users"}

	ws.Route(ws.GET("/").To(u.findAllUsers).
		// docs
		Doc("get all users").
		Metadata(restfulspec.KeyOpenAPITags, tags).
		Writes([]User{}).
		Returns(200, "OK", []User{}))

	ws.Route(ws.GET("/{user-id}").To(u.findUser).
		// docs
		Doc("get a user").
		Param(ws.PathParameter("user-id", "identifier of the user").DataType("integer").DefaultValue("1")).
		Metadata(restfulspec.KeyOpenAPITags, tags).
		Writes(User{}). // on the response
		Returns(200, "OK", User{}).
		Returns(404, "Not Found", nil))

	ws.Route(ws.PUT("/{user-id}").To(u.updateUser).
		// docs
		Doc("update a user").
		Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
		Metadata(restfulspec.KeyOpenAPITags, tags).
		Reads(User{})) // from the request

	ws.Route(ws.PUT("").To(u.createUser).
		// docs
		Doc("create a user").
		Metadata(restfulspec.KeyOpenAPITags, tags).
		Reads(User{})) // from the request

	ws.Route(ws.DELETE("/{user-id}").To(u.removeUser).
		// docs
		Doc("delete a user").
		Metadata(restfulspec.KeyOpenAPITags, tags).
		Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")))

	return ws
}

// GET http://localhost:8080/users
//
func (u UserResource) findAllUsers(request *restful.Request, response *restful.Response) {
	list := []User{}
	for _, each := range u.users {
		list = append(list, each)
	}
	response.WriteEntity(list)
}

// GET http://localhost:8080/users/1
//
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
	id := request.PathParameter("user-id")
	usr := u.users[id]
	if len(usr.ID) == 0 {
		response.WriteErrorString(http.StatusNotFound, "User could not be found.")
	} else {
		response.WriteEntity(usr)
	}
}

// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
//
func (u *UserResource) updateUser(request *restful.Request, response *restful.Response) {
	usr := new(User)
	err := request.ReadEntity(&usr)
	if err == nil {
		u.users[usr.ID] = *usr
		response.WriteEntity(usr)
	} else {
		response.WriteError(http.StatusInternalServerError, err)
	}
}

// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa</Name></User>
//
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
	usr := User{ID: request.PathParameter("user-id")}
	err := request.ReadEntity(&usr)
	if err == nil {
		u.users[usr.ID] = usr
		response.WriteHeaderAndEntity(http.StatusCreated, usr)
	} else {
		response.WriteError(http.StatusInternalServerError, err)
	}
}

// DELETE http://localhost:8080/users/1
//
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
	id := request.PathParameter("user-id")
	delete(u.users, id)
}

func main() {
	u := UserResource{map[string]User{}}
	restful.DefaultContainer.Add(u.WebService())

	config := restfulspec.Config{
		WebServices: restful.RegisteredWebServices(), // you control what services are visible
		APIPath:     "/apidocs.json",
		PostBuildSwaggerObjectHandler: enrichSwaggerObject}
	restful.DefaultContainer.Add(restfulspec.NewOpenAPIService(config))

	// 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{"Content-Type", "Accept"},
		AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
		CookiesAllowed: false,
		Container:      restful.DefaultContainer}
	restful.DefaultContainer.Filter(cors.Filter)

	log.Printf("Get the API using http://localhost:8080/apidocs.json")
	log.Printf("Open Swagger UI using http://swagger-ui:8082/?url=http://localhost:8080/apidocs.json")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func enrichSwaggerObject(swo *spec.Swagger) {
	swo.Info = &spec.Info{
		InfoProps: spec.InfoProps{
			Title:       "UserService",
			Description: "Resource for managing Users",
			Contact: &spec.ContactInfo{
				ContactInfoProps:spec.ContactInfoProps{
					Name:  "john",
					Email: "john@doe.rp",
					URL:   "http://johndoe.org",
				},
			},
			License: &spec.License{
				LicenseProps:spec.LicenseProps{
					Name: "MIT",
					URL:  "http://mit.org",
				},
			},
			Version: "1.0.0",
		},
	}
	swo.Tags = []spec.Tag{spec.Tag{TagProps: spec.TagProps{
		Name:        "users",
		Description: "Managing users"}}}
}

// User is just a sample type
type User struct {
	ID   string `json:"id" description:"identifier of the user"`
	Name string `json:"name" description:"name of the user" default:"john"`
	Age  int    `json:"age" description:"age of the user" default:"21"`
}

第二步: 启用服务

# 下载依赖
go mod init
go mod tidy

# 运行服务
go run main.go

第三步: 服务访问
swagger-ui 服务地址: http://192.168.139.139:8082/

restfulApi 服务地址: http://10.188.136.34:8080/apidocs.json

对应服务的访问界面

http://192.168.139.139:8082/?url=http://10.188.136.34:8080/apidocs.json

总结

随着互联网领域的发展和演进,服务也从之前的单体应用,分布式,微服务,函数服务演进,伴随着微服务的拆分,多种开发语言的协作边界逐渐模糊,一个项目中往往涉及多种开发语言开发适合的模块从而更好的构建整体项目的稳定高效运行,swagger 作为一种调试框架,对于服务接口拆分和多模块调式也展示了非常重要的作用;因此本文基于回顾和实践总结的方式完成了基于 restfulAPI 的开发调试,对于后续的项目也会有一定的助益;

林花谢了春红,又匆匆,无奈朝来寒雨晚来风。

posted @ 2023-01-29 18:52  流雨声  阅读(798)  评论(0编辑  收藏  举报