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

golang 微服务(2) Restful 服务(golang web框架 restful)

nanshan 2024-11-05 10:47 10 浏览 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

相关推荐

0722-6.2.0-如何在RedHat7.2使用rpm安装CDH(无CM)

文档编写目的在前面的文档中,介绍了在有CM和无CM两种情况下使用rpm方式安装CDH5.10.0,本文档将介绍如何在无CM的情况下使用rpm方式安装CDH6.2.0,与之前安装C5进行对比。环境介绍:...

ARM64 平台基于 openEuler + iSula 环境部署 Kubernetes

为什么要在arm64平台上部署Kubernetes,而且还是鲲鹏920的架构。说来话长。。。此处省略5000字。介绍下系统信息;o架构:鲲鹏920(Kunpeng920)oOS:ope...

生产环境starrocks 3.1存算一体集群部署

集群规划FE:节点主要负责元数据管理、客户端连接管理、查询计划和查询调度。>3节点。BE:节点负责数据存储和SQL执行。>3节点。CN:无存储功能能的BE。环境准备CPU检查JDK...

在CentOS上添加swap虚拟内存并设置优先级

现如今很多云服务器都会自己配置好虚拟内存,当然也有很多没有配置虚拟内存的,虚拟内存可以让我们的低配服务器使用更多的内存,可以减少很多硬件成本,比如我们运行很多服务的时候,内存常常会满,当配置了虚拟内存...

国产深度(deepin)操作系统优化指南

1.升级内核随着deepin版本的更新,会自动升级系统内核,但是我们依旧可以通过命令行手动升级内核,以获取更好的性能和更多的硬件支持。具体操作:-添加PPAs使用以下命令添加PPAs:```...

postgresql-15.4 多节点主从(读写分离)

1、下载软件[root@TX-CN-PostgreSQL01-252software]#wgethttps://ftp.postgresql.org/pub/source/v15.4/postg...

Docker 容器 Java 服务内存与 GC 优化实施方案

一、设置Docker容器内存限制(生产环境建议)1.查看宿主机可用内存bashfree-h#示例输出(假设宿主机剩余16GB可用内存)#Mem:64G...

虚拟内存设置、解决linux内存不够问题

虚拟内存设置(解决linux内存不够情况)背景介绍  Memory指机器物理内存,读写速度低于CPU一个量级,但是高于磁盘不止一个量级。所以,程序和数据如果在内存的话,会有非常快的读写速度。但是,内存...

Elasticsearch性能调优(5):服务器配置选择

在选择elasticsearch服务器时,要尽可能地选择与当前业务量相匹配的服务器。如果服务器配置太低,则意味着需要更多的节点来满足需求,一个集群的节点太多时会增加集群管理的成本。如果服务器配置太高,...

Es如何落地

一、配置准备节点类型CPU内存硬盘网络机器数操作系统data节点16C64G2000G本地SSD所有es同一可用区3(ecs)Centos7master节点2C8G200G云SSD所有es同一可用区...

针对Linux内存管理知识学习总结

现在的服务器大部分都是运行在Linux上面的,所以,作为一个程序员有必要简单地了解一下系统是如何运行的。对于内存部分需要知道:地址映射内存管理的方式缺页异常先来看一些基本的知识,在进程看来,内存分为内...

MySQL进阶之性能优化

概述MySQL的性能优化,包括了服务器硬件优化、操作系统的优化、MySQL数据库配置优化、数据库表设计的优化、SQL语句优化等5个方面的优化。在进行优化之前,需要先掌握性能分析的思路和方法,找出问题,...

Linux Cgroups(Control Groups)原理

LinuxCgroups(ControlGroups)是内核提供的资源分配、限制和监控机制,通过层级化进程分组实现资源的精细化控制。以下从核心原理、操作示例和版本演进三方面详细分析:一、核心原理与...

linux 常用性能优化参数及理解

1.优化内核相关参数配置文件/etc/sysctl.conf配置方法直接将参数添加进文件每条一行.sysctl-a可以查看默认配置sysctl-p执行并检测是否有错误例如设置错了参数:[roo...

如何在 Linux 中使用 Sysctl 命令?

sysctl是一个用于配置和查询Linux内核参数的命令行工具。它通过与/proc/sys虚拟文件系统交互,允许用户在运行时动态修改内核参数。这些参数控制着系统的各种行为,包括网络设置、文件...

取消回复欢迎 发表评论: