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

golang 微服务(2) Restful 服务

toyiye 2024-06-21 11:59 7 浏览 0 评论

Restful 服务

我们之前搭建了一个简单 Http 服务,接下来我们更进一步,restful 服务,有关 restful 表示 represent a representational state transfer ,大部分人因为 restful 就是在 http 请求中使用数据格式 json 格式。restful 更像一种模式一种规范,通过实现 restful 规范的 http 请求无论设计人员和开发人员更容易理解和使用,也可以说一种协议,遵循 restful 定义规则来设计出会说话 api。将增删改查这些动作有 http 请求 method (作为动词)来表意,而不是通过路由上来定义。
在 restful 请求和返回数据格式都使用 json 格式,json 好处是便于序列化和反序列化。


图玩具

今天我们分享不想玩具,而是一个真正产品级别项目,希望大家有耐心给我一起 coding 下去,同时我相信您也会不虚此行。我们先定义数据,还是老规矩先从简单我们在文件定义数据,而不是上来就引入数据库结构如下,我们现在最热衷两门语言。


定义数据结构

这里我们定义 Tut(教程)作为数据,数据有以下字段,分别是ID,教程名称、描述和价格等。


type Tut struct {
	ID          int
	Name        string
	Description string
	Price       float32
	Category    string
	CreatedOn   string
	UpdatedOn   string
	DeletedOn   string
}

为了演示 resetful 构建,我们暂时模拟出一些数据,并没有引入数据库,这是因为今天主角是 Restful 服务而非数据库存储。


var tutList = []*Tut{
	&Tut{
		ID:          1,
		Name:        "golang",
		Description: "golang basic",
		Price:       2.45,
		Category:    "web",
		CreatedOn:   time.Now().UTC().String(),
		UpdatedOn:   time.Now().UTC().String(),
	},
	&Tut{
		ID:          2,
		Name:        "rust",
		Description: "rust blockchain",
		Price:       1.99,
		Category:    "blockchain",
		CreatedOn:   time.Now().UTC().String(),
		UpdatedOn:   time.Now().UTC().String(),
	},
}

然后就定义路由的处理程序来将数据返回给客户端。


创建 Tuts 结构体

我们这里定义 Tuts 结构体,这个结构体对应 Tut 结构体(类)的集合,可以理解为表,我们对 Tut 集合操作都是由,


type Tuts struct {
	l *log.Logger
}

随着开发时间,接触业务逐渐复杂,越来越感觉到日志重要性,以及设计好的日志系统对运维人员会有多大帮助,好的日志系统是健壮系统的一个保证。结构体中我们将日志作为结构体属性由外部传入。


定创建 Tuts 的方法


func NewTuts(l *log.Logger) *Tuts {
	return &Tuts{l}
}


实现 ServeHTTP 方法

让结构体实现了 ServeHTTP 方法,从而结构体就是 Handler 接口类型,这点一个点如果是从面向对象语言走出来的程序员还需要花一些时间和精力来理解。一旦理解就发现这种语言设计就是顺理成章,设计巧妙。


func (t *Tuts) ServeHTTP(rw http.ResponseWriter, r *http.Request) {

}

接下来我们创建 NewServeMux 替换 http 提供默认 ServeMux 路由管理器,使用 NewTuts 创建一个路由 Handler 因为 Tuts 结构体实现了 ServeHTTP 方法所以就可以做完参数传入到 Handle 方法来处理路由 / 的请求。


    tutsHandler := handlers.NewTuts(l)
    goodbyeHandler := handlers.NewGoodbye(l)

    sm := http.NewServeMux()
    sm.Handle("/", tutsHandler)
    sm.Handle("/goodbye", goodbyeHandler)


序列化

使用 json 包提供 Marshal 方法来对于结构体进行序列化为 json 格式,这个方法会返回一个json格式字符串和一个错误 error 值,我们现在已经熟悉了 go 编程 style 我们需要 error 进行处理。


func (t *Tuts) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	lt := data.GetTuts()
	//Marshal 返回值 ([]byte, error),
	d, err := json.Marshal(lt)
	if err != nil {
		http.Error(rw, "Unable to marshal json", http.StatusInternalServerError)
	}
	rw.Write(d)
}

在终端运行 curl 命令来访问服务查看 json 解析是否已经将结构体解析 json 格式


curl localhost:4600


[{"ID":1,"Name":"golang","Description":"golang basic","Price":2.45,"Category":"web","CreatedOn":"2020-05-01 03:42:41.044691 +0000 UTC","UpdatedOn":"2020-05-01 03:42:41.044693 +0000 UTC","DeletedOn":""},{"ID":2,"Name":"rust","Description":"rust blockchain","Price":1.99,"Category":"blockchain","CreatedOn":"2020-05-01 03:42:41.044694 +0000 UTC","UpdatedOn":"2020-05-01 03:42:41.044696 +0000 UTC","DeletedOn":""}]

在返回数据项 ID 或者 Name 这些字段首字母都是大写的,从 javascript 和 java 走出来的程序员会有点怪怪感觉,可能 csharp 就不会有这样感觉,因为csharp 方法名就是大写开头,我们没有接触过 csharp,在 go 语言字段和方法名大小开头表示共有属性或方法,我们想要解析为小写(遵循驼峰命名规范)


type Tut struct {
	ID          int     `json:"id"`
	Name        string  `json:"name"`
	Description string  `json:"description"`
	Price       float32 `json:"price"`
	Category    string  `json:"category"`
	CreatedOn   string  `json:"-"`
	UpdatedOn   string  `json:"-"`
	DeletedOn   string  `json:"-"`
}

再次通过终端输出,查看效果。


[{"id":1,"name":"golang","description":"golang basic","price":2.45,"category":"web"},{"id":2,"name":"rust","description":"rustblockchain","price":1.99,"category":"blockchain"}](


序列化和反序列化

在 go 语言中 json 包具有 Encode 和 Decode 包, 我们可以自己通过编码和解码器来自己实现一个 json 序列化和反序列方法。为什么我们要自己实现 json 解析器呢,这是为了以后通过提高效率,通过协程来处理一些大型数据结构的解析。


func NewEncoder(w io.Writer) *Encoder

调用 NewEncoder 的 Encoder ,NewEncoder 需要传入一个 io.Writer 接口,我们 http 方法就可以对的一个实现了 io.Writer 接口的


func (enc *Encoder) Encode(v interface{}) error

Encode 将结构体编码为符合 JSON 格式字节流数据,我们在处理程序可以得到 io.Writer


//编码器(序列化)
func (t *Tuts) ToJSON(w io.Writer) error {
	//获取json解码器
	e := json.NewEncoder(w)
	//调用解码器的解码方法来对 tuts 对象进行解码
	return e.Encode(t)
}

//解码器(反序列化)
func (t *Tut) FromJSON(r io.Reader) error {
	e := json.NewDecoder(r)
	return e.Decode(t)
}


实现 Restful 服务


func (t *Tuts) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet {
		t.getTuts(rw, r)
		return
	}
	//处理更新数据 POST
	if r.Method == http.MethodPost {
		t.addTut(rw, r)
		return
	}
	//处理 PUT 请求
	if r.Method == http.MethodPut {
		t.l.Println("Handle PUT Tuts")
		//正则解析器
		reg := regexp.MustCompile(`/([0-9]+)`)
		g := reg.FindAllStringSubmatch(r.URL.Path, -1)

		if len(g) != 1 {
			http.Error(rw, "Invalid URI", http.StatusBadRequest)
			return
		}

		if len(g[0]) != 2 {
			http.Error(rw, "Invalid URI", http.StatusBadRequest)
			return
		}
		idString := g[0][1]
		id, err := strconv.Atoi(idString)

		if err != nil {
			http.Error(rw, "Invalid URI", http.StatusBadRequest)
			return
		}
		t.l.Println("got id", id)

		t.updateTuts(id, rw, r)
	}

	rw.WriteHeader(http.StatusMethodNotAllowed)
}


r.Method == http.MethodPost

通过判断请求方式,来进行不同的动作,添加、更新和查询数据。我们是通过 http method 动词来表意进行区分,而不是在 url 中显示指明要进行动作。这里看起来 code 不那么优雅,接下来使用 Gollira 进行重构。在 PUT 中我们要做的是在如路由localhost:4600/id 来根据 id 对来更新符合条件 id 的数据进行更新。所以这里要相对复杂一下,PUT 做的更新动作,更新会分解为查找和替换两个动作,所以相对复杂一些。


reg := regexp.MustCompile(`/([0-9]+)`)
g := reg.FindAllStringSubmatch(r.URL.Path, -1)

if len(g) != 1 {
	http.Error(rw, "Invalid URI", http.StatusBadRequest)
	return
}

if len(g[0]) != 2 {
	http.Error(rw, "Invalid URI", http.StatusBadRequest)
	return
}
idString := g[0][1]
id, err := strconv.Atoi(idString)


func (re*Regexp) FindAllStringSubmatch(s string,n int)[][]string

这个方法返回 [][]string,
MustCompile 相对于 Compile 少一个返回值 err,取而代之 MustCompile 会抛出一个异常。会返回为一个二维 string 的数组,数组中会列出所有匹配内容,下面我们通过代码演示一下


func main() {
	fmt.Println("hello regexp")
	url := "http://localhost:4600/12"
	reg := regexp.MustCompile(`/([0-9]+)`)
	g := reg.FindAllStringSubmatch(url, -1)
	fmt.Printf("%v\n", g)
}


[[/12 12]]


url := "http://localhost:4600/12/1"


[[/12 12] [/1 1]]


func (t *Tuts) getTuts(rw http.ResponseWriter, r *http.Request) {
	t.l.Println("Handle GET products")
	lt := data.GetTuts()
	//Marshal 返回值 ([]byte, error),
	// d, err := json.Marshal(lt)
	err := lt.ToJSON(rw)
	if err != nil {
		http.Error(rw, "Unable to marshal json", http.StatusInternalServerError)
	}
	// rw.Write(d)

}

不满足我们想要 PUT、POST 或 GET 请求可以通过代码在头部给出信息说明对该 http 动作并没有处理


rw.WriteHeader(http.StatusMethodNotAllowed)

因为还没有定义 DELETE 方法所以执行下面 curl 语句时,头部会返回HTTP/1.1 405 Method Not Allowed信息。


curl localhost:4600 -X DELETE -v |jq


HTTP/1.1 405 Method Not Allowed


//解码器(反序列化)
func (t *Tuts) FromJSON(r io.Reader) error {
	e := json.NewDecoder(r)
	return e.Decode(t)
}


添加 Tut


func (t *Tuts) addTut(rw http.ResponseWriter, r *http.Request) {
	t.l.Println("Handle POST Tuts")
	tut := &data.Tut{}
	err := tut.FromJSON(r.Body)
	if err != nil {
		http.Error(rw, "Unable to unmarshal json", http.StatusBadRequest)
	}

	// t.l.Printf("tut:%#v", tut)
	data.AddTut(tut)
	//转换数据为tut
}


curl localhost:4600 -d '{"name":"react","description":"nice react tut"}'


[{"id":1,"name":"golang","description":"golang basic","price":2.45,"category":"web"},{"id":2,"name":"rust","description":"rust
blockchain","price":1.99,"category":"blockchain"},{"id":3,"name":"react","description":"nice react tut","price":0,"category":""
}]


	if r.Method == http.MethodPut {
		t.l.Println("Handle PUT Tuts")
		//正则解析器
		reg := regexp.MustCompile(`/([0-9]+)`)
		g := reg.FindAllStringSubmatch(r.URL.Path, -1)

		if len(g) != 1 {
			http.Error(rw, "Invalid URI", http.StatusBadRequest)
			return
		}

		if len(g[0]) != 2 {
			http.Error(rw, "Invalid URI", http.StatusBadRequest)
			return
		}
		idString := g[0][1]
		id, err := strconv.Atoi(idString)

		if err != nil {
			http.Error(rw, "Invalid URI", http.StatusBadRequest)
			return
		}
		t.l.Println("got id", id)
    }


添加 Tut 到集合


func AddTut(t *Tut) {
	t.ID = getNextID()
	tutList = append(tutList, t)
}


生成新的 ID


func getNextID() int {
	lt := tutList[len(tutList)-1]
	return lt.ID + 1
}


更新 Tut 方法


func (t *Tuts) updateTuts(id int, rw http.ResponseWriter, r *http.Request) {
	t.l.Println("Handle PUT Tuts")
	tut := &data.Tut{}
	err := tut.FromJSON(r.Body)
	if err != nil {
		http.Error(rw, "Unable to unmarshal json", http.StatusBadRequest)
	}

	err = data.UpdateTut(id, tut)
	if err == data.ErrTutNotFound {
		http.Error(rw, "tut not found", http.StatusNotFound)
		return
	}

	if err != nil {
		http.Error(rw, "tut not found", http.StatusInternalServerError)
		return
	}

}


更新 Tut 集合


func UpdateTut(id int, t *Tut) error {
	//根据 id 判断是否存在
	_, pos, err := findTut(id)
	if err != nil {
		return err
	}
	t.ID = id
	tutList[pos] = t

	return nil
}


查询 Tut(通过ID)


var ErrTutNotFound = fmt.Errorf("Tut not found")

func findTut(id int) (*Tut, int, error) {
	for l, t := range tutList {
		if t.ID == id {
			return t, l, nil
		}
	}

	return nil, -1, ErrTutNotFound
}



curl localhost:4600/1 -XPUT  -d '{"name":"react","description":"nice react tut"}'


curl localhost:4600
[{"id":1,"name":"react","description":"nice react tut","price":0,"category":""},{"id":2,"name":"rust","d

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码