Go语言学习 - RPC篇:gRPC-Gateway定制mux选项

简介: 通过上一讲,我们对gRPC的拦截器有了一定的认识,也能定制出很多通用的中间件。但在大部分的业务系统中,我们面向的还是HTTP协议。那么,今天我们就从gRPC-Gateway的mux选项出发,一起来看看一些很实用的特性。

概览

通过上一讲,我们对gRPC的拦截器有了一定的认识,也能定制出很多通用的中间件。

但在大部分的业务系统中,我们面向的还是HTTP协议。那么,今天我们就从gRPC-Gateway的mux选项出发,一起来看看一些很实用的特性。

ServeMux

import "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"

gwMux := runtime.NewServeMux()

// 函数签名
func NewServeMux(opts ...ServeMuxOption) *ServeMux {
   
}

我们将目标聚焦于这个ServeMux

  1. 目前官方区分v1和v2版本,版本不一致会导致很多编译上的问题
  2. 入参包括多个option选项函数,用于定制想要的mux内容

具体的内容可以参考ServeMux的数据结构,我这里挑选几个重点的能力:

type ServeMux struct {
   
    // 这个是server的核心实现:即注册的handlerf
    handlers                  map[string][]handler
  // 1、转发正常响应
    forwardResponseOptions    []func(context.Context, http.ResponseWriter, proto.Message) error
  // 2、序列化工具
    marshalers                marshalerRegistry
  // 3、进入时(http->grpc)的header匹配规则
    incomingHeaderMatcher     HeaderMatcherFunc
  // 4、返回时(grpc->http)的header匹配规则
    outgoingHeaderMatcher     HeaderMatcherFunc
  // 5、metadata的转换(从http转成grpc的md)
    metadataAnnotators        []func(context.Context, *http.Request) metadata.MD
  // 6、错误处理
    errorHandler              ErrorHandlerFunc
  // 7、流式错误处理
    streamErrorHandler        StreamErrorHandlerFunc
  // 8、路由错误
    routingErrorHandler       RoutingErrorHandlerFunc
}

综合一下,核心能力其实包括2块:

  1. Header数据的处理
  2. 返回消息的处理(包括正常情况和错误情况)

Header的数据处理

HTTP与gRPC协议头匹配

// HTTP -> gRPC
func WithIncomingHeaderMatcher(fn HeaderMatcherFunc) ServeMuxOption {
   
}

// gRPC -> HTTP
func WithOutgoingHeaderMatcher(fn HeaderMatcherFunc) ServeMuxOption {
   
}

type HeaderMatcherFunc func(string) (string, bool)

这个函数只做一个简单的映射,我们可以通过下面的例子开快速了解:

// 入参:header key
// 出参:返回header key,以及是否返回
func CustomMatcher(key string) (string, bool) {
   
    switch key {
   
    case "some-special-key":
        return key, true 
    case "deprecated-key":
        return "", false
    default:
    // 默认的匹配规则
        return runtime.DefaultHeaderMatcher(key)
    }
}

将HTTP头转成gRPC头

上面的matcher只是做一个key的映射,如果Header里包括更复杂的部分(例如Cookie),需要引入下面函数:

func WithMetadata(annotator func(context.Context, *http.Request) metadata.MD) ServeMuxOption {
   
    return func(serveMux *ServeMux) {
   
        serveMux.metadataAnnotators = append(serveMux.metadataAnnotators, annotator)
    }
}

我们注意两个点:

  1. 只做协议转换,不做逻辑处理(逻辑处理交给gRPC层的中间件统一处理)
  2. metadata.MD的底层数据结构为map[string][]string,与HTTP Header很类似

下面给出一个HTTP的Cookie处理示例:

// 示例
const (
  HTTP_COOKIE_TOKEN = "http_cookie"
  MD_TOKEN = "md_cookie"
)
func ExampleCookieMetadataAnnotator(ctx context.Context, r *http.Request) (md metadata.MD) {
   
  // 从HTTP的cookie中读出对应的数据
  c, err := r.Cookie(ODIN_JWT_TOKEN)
    if err != nil {
   
        return
    }

  // 将值放到md里,方便在后续提取
    return metadata.Pairs(MD_TOKEN, c.Value)
}

返回数据处理

正确返回

func WithForwardResponseOption(forwardResponseOption func(context.Context, http.ResponseWriter, proto.Message) error) ServeMuxOption {
   
}

正确返回时,核心的数据结构为 protoMessage。我们不妨做一个封装:

type HTTPResponse struct {
   
    Errno int         `json:"errno"`
    Msg   string      `json:"msg"`
    Data  interface{
   } `json:"data"`
}

func GatewayResponseModifier(ctx context.Context, w http.ResponseWriter, resp proto.Message) error {
   
  // 返回的数据,在外层同一封装了数据结构HTTPResponse,对一些历史项目兼容有很棒的效果
  newResp := &HTTPResponse{
   
    Data: resp,
  }
    pbData, _ := json.Marshal(newResp)
  w.Write(pbData)
  return nil
}

错误返回

func WithErrorHandler(fn ErrorHandlerFunc) ServeMuxOption {
   
}

错误处理在整个RPC框架中扮演了非常重要的角色,我们不妨通过如下例子来了解:

func GatewayErrModifier(ctx context.Context, mux *runtime.ServeMux, m runtime.Marshaler, w http.ResponseWriter, r *http.Request, err error) {
   
    // 提取error
    s, ok := status.FromError(err)
    // 非标准错误
    if !ok {
   
        runtime.DefaultHTTPErrorHandler(ctx, mux, m, w, r, err)
        return
    }

  // 对各类错误增加定制的逻辑
    switch s.Code() {
   
    case codes.Unauthenticated:
        // 示例:认证失败,可以加入重定向的逻辑
    default:
        runtime.DefaultHTTPErrorHandler(ctx, mux, m, w, r, err)
    }

    return
}

分析一下重点:

  1. error尽可能用gRPC标准的错误Status表示
  2. gRPC的标准错误,对错误码code有一套定义(参考google.golang.org/grpc/codes),类似于HTTP的状态码
  3. 错误码code要尽量少,过多没有意义
    1. 标准错误码尽可能复用,如资源找不到、权限不足等
    2. 业务错误码可以独立,一般一个系统定义1个即可

小结

本文重点介绍了gRPC-Gateway中2类ServeMux,也演示了对应的示例,大家能理解其基本用法即可。

后续,随着整体项目的落地,我会增加一些日常项目中常见的定制需求,帮助大家更好地认识RPC框架的能力。

目录
相关文章
|
1天前
|
JSON 安全 Java
2024年的选择:为什么Go可能是理想的后端语言
【4月更文挑战第27天】Go语言在2024年成为后端开发的热门选择,其简洁设计、内置并发原语和强大工具链备受青睐。文章探讨了Go的设计哲学,如静态类型、垃圾回收和CSP并发模型,并介绍了使用Gin和Echo框架构建Web服务。Go的并发通过goroutines和channels实现,静态类型确保代码稳定性和安全性,快速编译速度利于迭代。Go广泛应用在云计算、微服务等领域,拥有丰富的生态系统和活跃社区,适合作为应对未来技术趋势的语言。
8 0
|
1天前
|
安全 测试技术 Go
Golang深入浅出之-Go语言单元测试与基准测试:testing包详解
【4月更文挑战第27天】Go语言的`testing`包是单元测试和基准测试的核心,简化了测试流程并鼓励编写高质量测试代码。本文介绍了测试文件命名规范、常用断言方法,以及如何进行基准测试。同时,讨论了测试中常见的问题,如状态干扰、并发同步、依赖外部服务和测试覆盖率低,并提出了相应的避免策略,包括使用`t.Cleanup`、`t.Parallel()`、模拟对象和检查覆盖率。良好的测试实践能提升代码质量和项目稳定性。
7 1
|
1天前
|
运维 监控 Go
Golang深入浅出之-Go语言中的日志记录:log与logrus库
【4月更文挑战第27天】本文比较了Go语言中标准库`log`与第三方库`logrus`的日志功能。`log`简单但不支持日志级别配置和多样化格式,而`logrus`提供更丰富的功能,如日志级别控制、自定义格式和钩子。文章指出了使用`logrus`时可能遇到的问题,如全局logger滥用、日志级别设置不当和过度依赖字段,并给出了避免错误的建议,强调理解日志级别、合理利用结构化日志、模块化日志管理和定期审查日志配置的重要性。通过这些实践,开发者能提高应用监控和故障排查能力。
8 1
|
1天前
|
安全 Go
Golang深入浅出之-Go语言标准库中的文件读写:io/ioutil包
【4月更文挑战第27天】Go语言的`io/ioutil`包提供简单文件读写,适合小文件操作。本文聚焦`ReadFile`和`WriteFile`函数,讨论错误处理、文件权限、大文件处理和编码问题。避免错误的关键在于检查错误、设置合适权限、采用流式读写及处理编码。遵循这些最佳实践能提升代码稳定性。
5 0
|
1天前
|
Go C++
go 语言回调函数和闭包
go 语言回调函数和闭包
|
1天前
|
存储 负载均衡 监控
【Go 语言专栏】构建高可靠性的 Go 语言服务架构
【4月更文挑战第30天】本文探讨了如何利用Go语言构建高可靠性的服务架构。Go语言凭借其高效、简洁和并发性能,在构建服务架构中备受青睐。关键要素包括负载均衡、容错机制、监控预警、数据存储和服务治理。文章详细阐述了实现这些要素的具体步骤,通过实际案例分析和应对挑战的策略,强调了Go语言在构建稳定服务中的作用,旨在为开发者提供指导。
|
1天前
|
测试技术 Go 开发工具
【Go语言专栏】Go语言中的代码审查与最佳实践
【4月更文挑战第30天】Go语言因其简洁、高性能及并发能力,在云计算等领域广泛应用。代码审查对提升Go代码质量、遵循规范及团队协作至关重要。审查流程包括提交、审查、反馈、修改和合并代码。工具如GoLand、Git、ReviewBoard和GitHub提供支持。最佳实践包括遵循命名规范、添加注释、保持代码结构清晰、复用代码和确保测试覆盖。积极参与代码审查是提高质量的关键。
|
存储 IDE Java
go语言简单入门
go语言简单入门
139 0
|
缓存 搜索推荐 编译器
Go 语言入门很简单 -- 17. Go Package #私藏项目实操分享#
Go 语言入门很简单 -- 17. Go Package #私藏项目实操分享#
141 0
Go 语言入门很简单 -- 17. Go Package #私藏项目实操分享#
|
编译器 Shell 测试技术
Go 语言入门很简单--技巧和窍门(Tips and Tricks)
Go 语言入门很简单--技巧和窍门(Tips and Tricks)
154 0
Go 语言入门很简单--技巧和窍门(Tips and Tricks)