小工具      在线工具  汉语词典  css  js  c++  java

基于go-zero的api服务刨析并对比与gin的区别

# go-zero,golang,go-zero,http 额外说明

收录于:102天前

zero路由与gin的区别

官方网站归零

go-zero是一个微服务框架,融合了各种工程实践,集成了多种功能,比如服务各大API服务、RPC服务等。zero除了构建微服务项目外,还是一个性能优秀的Web框架,还可以构建单机网络应用程序。

更多移步www.w3cschool.cn/go-zero

Go 有很多 Web 框架。比如github上比较流行的有:

  1. 杜松子酒 Go语言编写的HTTP Web框架,它以更好的性能实现了类似Martini的API,性能更好。
  2. 贝戈 面向Go编程语言的开源高性能web框架。
  3. 鸢尾花最快的Go语言Web框架,完备MVC支持
  4. 回声 高性能、极简Go语言Web框架。

这些框架的路由方法非常相似。大致步骤是通过工具库提供的方法构建Web引擎、配置路由、启动Web服务器、配置监听端口等,如下:

//gin框架
r := gin.Default()
_ = r.Run()
// 或者启动原生服务
manners.ListenAndServe(":8888", r)
//gin框架配置路由
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
    
    c.String(http.StatusOK, "pong")
})

//路由组和其他路由方式
//通过路由将处理函数配置

gin框架中路由的处理函数是不限个数,从而实现了中间件的功能,另外gin.Context是路由的上下文连接,在不同路由路径下会自动解析该路径下的请求参数。

//iris服务

app := iris.New()
app.Run(iris.Addr(":8080"))

// 或者自定义链接方式与端口号
l, err := listenerCfg.NewListener("tcp", ":8080")
if err != nil {
    
    app.Logger().Fatal(err)
}
app.Run(iris.Listener(l))

// 或者启动原生服务
app.Run(iris.Raw(&http.Server{
    Addr:":8080"}).ListenAndServe)
//iris路由

app.Get("/", func(ctx iris.Context) {
    
    ctx.HTML("<h1> Hello from /contact </h1>")
})

这些框架的路由原理与通过上下文连接Web容器非常相似,每个控制器独立解析路由路径下的参数。

在示例项目中,路由需要分离,处理逻辑也需要分离。对于基于这个原则的Web框架来说,同一系列的路由路径都是由一个接口或者类成员方法来实现的(所有方法的分散不利于代码维护)。那么实现就和Java的实现类似了。

在这里插入图片描述

如图,/a1,/a2...都是/a系列的,对应A类,要分发到对应的路由,只需通过A类调用对应的方法即可。

//main

import (
	"github.com/gin-gonic/gin"
	"backend/controller"
)
func main() {
    

	//创建一个服务器引擎
	engine := gin.Default()

	//控制器传参
	c := new(controller.IndexController)
	c.RegisterRoute(engine)

	//配置服务器端口
	engine.Run("127.0.0.1:8080")

}
// controller
type IndexController struct{
    }

func (self IndexController) RegisterRoute(g *gin.Engine) {
    
	g.GET("/user", self.getUer)
	g.GET("/banner")
}

func (IndexController) getUer(c *gin.Context) {
    
	lo := logic.IndexLogic{
    }
	notice := lo.GetUser()
	fmt.Println(notice)
}

杜松子酒路线分销案例

不同gin等路由分发的方式go-zero自成一派,具体请看归零路由机制分析。zero是使用路由注册的方式,如下图所示:

在这里插入图片描述
编写该路由下的逻辑处理函数通过通过服务器引擎提供的方法将处理逻辑注册到对应路由中。这种方法的耦合度更低,显然这也更贴合go语言的特性。

//zero web服务

import (
	"fmt"

	"demo/apiservice/internal/config"
	"demo/apiservice/internal/handler"
	"demo/apiservice/internal/svc"

	"github.com/zeromicro/go-zero/rest"
)

func main() {
    
	
	var c config.Config
	c.Host = "0.0.0.0"
	c.Port = 8000
	server := rest.MustNewServer(c.RestConf)
	defer server.Stop()
	// head
	ctx := svc.NewServiceContext(c)
	handler.RegisterHandlers(server, ctx)
	// boot
	fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
	server.Start()
}

上面的代码中,head to boot部分是服务器配置,并不是实现Web服务器实例的主要方法。这部分主要是配置第三方框架时需要的,比如日志文件等,核心代码如下

//c.RestConf服务器配置(略)

server := rest.MustNewServer(c.RestConf)
server.Start()

路线如下:

import (
	"net/http"
	"demo/apiservice/internal/svc"
	"github.com/zeromicro/go-zero/rest"
)

func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
    
	server.AddRoutes(
		[]rest.Route{
    
			{
    
				Method:  http.MethodGet,
				Path:    "/from/:name",
				Handler: ApiserviceHandler(serverCtx),
			},
			//自定义路由
			{
    
				Method: http.MethodPost,
				Path: "/create",
				Handler: OrderCreateController(),
			},
		},
	)
}

路由通过注册的方式直接与函数绑定,完全没有结构体方法的继承关系,直接将函数作为第一操作单元。

路由处理函数

zero的路由处理函数满足的条件是返回类型为http.HandlerFunc的函数。细心的小伙伴可以就发现了这不是gin的路由处理函数的返回类型吗?实际上zero中HandlerFunc是基于Go原生的http库,而gin框架中该类型是对http.HandlerFunc的进一步封装。

在这里插入图片描述
http.HandlerFunc是一个参数为ResponseWriter,*Request的函数,学过Java的是不是秒懂了,在Java的Servlet中也存在HttpRquestHttpReponse,且该函数是请求报文与响应报文的封装对象,通过该对象可以直接获取请求参数和设置响应参数。

在zero中也是同样的原理,对于这样个参数如何获取获取请求参数和设置响应参数就不做过多介绍,有兴趣的看看源码。zero封装了httpx对这两个对象实现了解析,通过httpx库会更加方法的使用这两个参数。

只要满足返回类型为http.HandlerFunc就是zero的逻辑处理函数。

zero获取请求参数

前一节说到http.HandlerFunc类型的函数为zero的逻辑处理函数,而该类型又是参数必须是func(w http.ResponseWriter, r *http.Request)的函数。zero封装了httpx库提供了更加方便简洁方式使用这两个参数。

官方教程:github.com/zeromicro/go-zero/rest/httpx

httpx库内容不多,看源码很容器看懂,就是四种请求参数的解析。

在这里插入图片描述

package httpx

import (
	"io"
	"net/http"
	"strings"
	"sync/atomic"

	"github.com/zeromicro/go-zero/core/mapping"
	"github.com/zeromicro/go-zero/core/validation"
	"github.com/zeromicro/go-zero/rest/internal/encoding"
	"github.com/zeromicro/go-zero/rest/internal/header"
	"github.com/zeromicro/go-zero/rest/pathvar"
)

const (
	formKey           = "form"
	pathKey           = "path"
	maxMemory         = 32 << 20 // 32MB
	maxBodyLen        = 8 << 20  // 8MB
	separator         = ";"
	tokensInAttribute = 2
)

var (
	formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues())
	pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues())
	validator       atomic.Value
)

// Validator defines the interface for validating the request.
type Validator interface {
    
	// Validate validates the request and parsed data.
	Validate(r *http.Request, data any) error
}

// Parse parses the request.
func Parse(r *http.Request, v any) error {
    
	if err := ParsePath(r, v); err != nil {
    
		return err
	}

	if err := ParseForm(r, v); err != nil {
    
		return err
	}

	if err := ParseHeaders(r, v); err != nil {
    
		return err
	}

	if err := ParseJsonBody(r, v); err != nil {
    
		return err
	}

	if valid, ok := v.(validation.Validator); ok {
    
		return valid.Validate()
	} else if val := validator.Load(); val != nil {
    
		return val.(Validator).Validate(r, v)
	}

	return nil
}

// ParseHeaders parses the headers request.
func ParseHeaders(r *http.Request, v any) error {
    
	return encoding.ParseHeaders(r.Header, v)
}

// ParseForm parses the form request.
func ParseForm(r *http.Request, v any) error {
    
	params, err := GetFormValues(r)
	if err != nil {
    
		return err
	}

	return formUnmarshaler.Unmarshal(params, v)
}

// ParseHeader parses the request header and returns a map.
func ParseHeader(headerValue string) map[string]string {
    
	ret := make(map[string]string)
	fields := strings.Split(headerValue, separator)

	for _, field := range fields {
    
		field = strings.TrimSpace(field)
		if len(field) == 0 {
    
			continue
		}

		kv := strings.SplitN(field, "=", tokensInAttribute)
		if len(kv) != tokensInAttribute {
    
			continue
		}

		ret[kv[0]] = kv[1]
	}

	return ret
}

// ParseJsonBody parses the post request which contains json in body.
func ParseJsonBody(r *http.Request, v any) error {
    
	if withJsonBody(r) {
    
		reader := io.LimitReader(r.Body, maxBodyLen)
		return mapping.UnmarshalJsonReader(reader, v)
	}

	return mapping.UnmarshalJsonMap(nil, v)
}

// ParsePath parses the symbols reside in url path.
// Like http://localhost/bag/:name
func ParsePath(r *http.Request, v any) error {
    
	vars := pathvar.Vars(r)
	m := make(map[string]any, len(vars))
	for k, v := range vars {
    
		m[k] = v
	}

	return pathUnmarshaler.Unmarshal(m, v)
}

// SetValidator sets the validator.
// The validator is used to validate the request, only called in Parse,
// not in ParseHeaders, ParseForm, ParseHeader, ParseJsonBody, ParsePath.
func SetValidator(val Validator) {
    
	validator.Store(val)
}

func withJsonBody(r *http.Request) bool {
    
	return r.ContentLength > 0 && strings.Contains(r.Header.Get(header.ContentType), header.ApplicationJson)
}

  1. xxx?a=xxx?&b=xxx类型

该类型可以分为超链接和表单,对应的方法为httpx.ParseForm

  1. /xxx/:a/:b类型参数

对应方法为httpx.ParsePath

  1. 请求头参数

对应方法为http.ParseHeaders

4.请求体参数

对应方法为httpx.ParseJsonBody

httpx为zero库下的github.com/zeromicro/go-zero/rest/httpx

另外还有httpx.Parse方法,能够自动解析,对应参数,源码如下:

// Parse parses the request.
func Parse(r *http.Request, v any) error {
    
	if err := ParsePath(r, v); err != nil {
    
		return err
	}

	if err := ParseForm(r, v); err != nil {
    
		return err
	}

	if err := ParseHeaders(r, v); err != nil {
    
		return err
	}

	if err := ParseJsonBody(r, v); err != nil {
    
		return err
	}

	if valid, ok := v.(validation.Validator); ok {
    
		return valid.Validate()
	} else if val := validator.Load(); val != nil {
    
		return val.(Validator).Validate(r, v)
	}

	return nil
}

zero设置响应参数

http.HandlerFunc函数可知,路由处理函数是一个字符流,用于写入响应数据。响应数据就相对复杂了,应为响应数据一般要包含响应码,而响应吗数量非常多从100~600之间都有响应码,因此返回携带准确响应码的就十分棘手。

HTTP协议

一般来说,大多数框架响应代码都是开发人员定义的。响应分为五类:信息响应 (100–199)、成功响应 (200–299)、重定向 (300–399)、客户端错误 (400–499) 和服务器错误 (500–599)。

最常用的响应代码是:

  • 200 - 请求成功
  • 301 - 资源(网页等)已永久移动到另一个 URL
  • 400 - 客户端请求的语法不正确,服务器无法理解。
  • 403 - 服务器理解客户端的请求,但拒绝执行它
  • 404 - 请求的资源(网页等)不存在
  • 500内部服务器错误
  • 502 - 充当网关或代理的服务器在尝试执行请求时收到来自远程服务器的无效响应。

在这里插入图片描述

httpx库中只有三种种响应状态,成功、失败和自定义。前者是以Ok为前缀的方法,后者是以Error为前缀的方法。

// Error writes err into w.
func Error(w http.ResponseWriter, err error, fns ...func(w http.ResponseWriter, err error)) {
    
	lock.RLock()
	handler := errorHandler
	lock.RUnlock()

	doHandleError(w, err, handler, WriteJson, fns...)
}

// ErrorCtx writes err into w.
func ErrorCtx(ctx context.Context, w http.ResponseWriter, err error,
	fns ...func(w http.ResponseWriter, err error)) {
    
	lock.RLock()
	handlerCtx := errorHandlerCtx
	lock.RUnlock()

	var handler func(error) (int, any)
	if handlerCtx != nil {
    
		handler = func(err error) (int, any) {
    
			return handlerCtx(ctx, err)
		}
	}
	writeJson := func(w http.ResponseWriter, code int, v any) {
    
		WriteJsonCtx(ctx, w, code, v)
	}
	doHandleError(w, err, handler, writeJson, fns...)
}

// Ok writes HTTP 200 OK into w.
func Ok(w http.ResponseWriter) {
    
	w.WriteHeader(http.StatusOK)
}

// OkJson writes v into w with 200 OK.
func OkJson(w http.ResponseWriter, v any) {
    
	WriteJson(w, http.StatusOK, v)
}

// OkJsonCtx writes v into w with 200 OK.
func OkJsonCtx(ctx context.Context, w http.ResponseWriter, v any) {
    
	WriteJsonCtx(ctx, w, http.StatusOK, v)
}

当对响应数据的需求不高时,可以直接使用这两种方法。当需要自定义响应参数时,需要使用自定义的返回响应消息。

// WriteJson writes v as json string into w with code.
func WriteJson(w http.ResponseWriter, code int, v any) {
    
	if err := doWriteJson(w, code, v); err != nil {
    
		logx.Error(err)
	}
}

// WriteJsonCtx writes v as json string into w with code.
func WriteJsonCtx(ctx context.Context, w http.ResponseWriter, code int, v any) {
    
	if err := doWriteJson(w, code, v); err != nil {
    
		logx.WithContext(ctx).Error(err)
	}
}

WriteJsonWriteJsonCtx可以定义返回状态码即code参数。前者不带配置项,后者携带配置项。v为响应体数据,为任意类型。由函数名可以看出返回类型为json字符串,由于http时字符流传输,json字符串是前后端传输的最轻量级的数据传输格式。

v是任意类型,也就是说,任何类型传参后都由框架自动序列化为json字符串。

// create
func OrderCreateController() http.HandlerFunc {
    
	return func(w http.ResponseWriter, r *http.Request) {
    
		//获取请求参数
		var req models.Order
		err := httpx.ParseJsonBody(r, &req)
		if err != nil {
    
			//fmt.Printf("ordercontoller err:%v", err)
			httpx.WriteJson(w, 500, fmt.Sprintf("ordercontoller err:%v", err))
			return
		}
		err = orderlogic.OrderLogic.Create(req)
		if err != nil {
    
			//fmt.Printf("order create err:%v", err)
			httpx.WriteJson(w, 500, fmt.Sprintf("order create err:%v", err))
			return
		}
		httpx.OkJson(w, map[string]string{
    "code": "200", "message": "插入成功!"})

	}
}

在这里插入图片描述

. . .

相关推荐

额外说明

java注解计算12生肖,获取java数据中的年份,根据生日日期获取生肖注解,根据输入时间获取生肖,通过自定义注解获取生肖,根据生肖获取生肖年份和时间

        最近,开发中需要增加生肖,但是不想增加字段,于是通过注解的方式,实现生日与生肖的转换。         话不多说,直接上代码,如下:  实体类中的字段,添加自定义注解(ToChineseZodiacSerializer): /** * 生

额外说明

Java中synchronized:特性、使用、锁机制与策略简析

目录 synchronized的特性 互斥性 可见性 可重入性 synchronized的使用方法 synchronized的锁机制 常见锁策略 乐观锁与悲观锁 重量级锁与轻量级锁 公平锁与非公平锁 可重入锁与不可重入锁 自旋锁 读写锁 synchron

额外说明

【Python零基础到入门】Python预备知识必备篇——Python简介

目录 - 前言 -Python简介 -Python诞生背景 -Python 特点 -编程语言排行榜 -总结 - 前言 本文章是【Python零基础到入门专栏】学习的系列文章 Python专栏 传送门 在此:https://blog.csdn.net/zh

额外说明

【Unity3D日常开发】Unity中如何在不知道组件是否添加的情况下添加组件

推荐阅读 CSDN主页 GitHub开源地址 Unity3D插件分享 简书地址 我的个人博客 QQ群:1040082875 大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 如何在不知道组件是否已经

额外说明

[CMake教程] 使用变量

目录 4.1 定义变量(赋值) 4.2 引用变量(取值) 4.3 一些常用的CMAKE变量 CMake 同样可以使用变量,比如当依赖文件过多或需要生成的项目繁杂,就可以使用变量统一管理,也便于以后的条件编译。 CMake 变量分为环境变量和普通变量: 环

额外说明

解决Win10系统ping不通、无法远程的问题

1、概述        某天要使用微软的远程桌面程序mstsc.exe远程到旁边的一台测试电脑上,结果远程不了,ping都ping不通,于是详细研究了这个问题。在此大概地记录一下该问题排查的过程,以供参考。      2、ping不通         使

额外说明

SpringAop 源码解析 (二) - 代理对象的创建以及执行过程

一、SpringAop 代理创建 以及 执行过程 在上篇文章中分析得出在使用 Aop 时,实际向 Spring 容器中注入了一个 AnnotationAwareAspectJAutoProxyCreator 动态代理 bean 生成处理器,该类有实现 B

额外说明

【PTA篇】浙大版《C语言程序设计(第3版)》题目集 练习2-4

练习2-4 温度转换 (5分)  本题要求掌握printf()函数的格式化输出。 答案: #include "stdio.h" int main() { int F=150,C; C=5*(F-32)/9; printf("fa

额外说明

MyBatis的SqlSession理解

SqlSession是Mybatis最重要的构建之一,可以认为Mybatis一系列的配置目的是生成类似JDBC生成的Connection对象的statement对象,这样才能与数据库开启“沟通”,通过SqlSession可以实现增删改查(当然现在更加推荐

额外说明

微信公众号 Spring Cloud 相关文章链接备份(纯技术)

PassJava 学习教程 https://github.com/Jackson0714/PassJava-Platform 推荐 7 个牛哄哄 Spring Cloud 实战项目 https://mp.weixin.qq.com/s/wu3VRn0Nu

ads via 小工具