Skip to content

网关逻辑之请求体解析

977字约3分钟

2024-10-21

常见的Body类型

body解析比较特殊,不同的body类型需要不同的解析方式,而决定解析方式的是 header 中的 content_type属性 , 当前常见的Body类型如下:

  • application/json : json格式的请求
  • application/x-www-form-urlencoded : 表单默认的提数据格式, k/v 均为字符串
  • application/xml : xml格式的请求
  • application/yml 、application/yaml : yaml格式的请求
  • multipart/form-data : 一般用于文件上传(本教程暂不考虑文件上传)

问题分析

问题其实很明显,核心就是 如何支持多种多样的请求类型 ,一般而言,面对此类问题,主要是如下两个解决方案:

  • 请求网关接口,仅支持一种请求类型,如:仅支持json请求,此种解决方案的优缺点都很明显:
    • 优点:对于应用网关复杂问题简化,仅支持一种请求方式,具有明确的输入类型与规则,无歧义,无复杂适配
    • 缺点:对于使用方,可能会存在较高的调用改造成本。如:以前通过 xml 请求,现在要升级成通过 json 请求,需要升级网络库等基础组件,成本高,影响大
  • 请求网关接口,支持任意的请求类型,此种解决方案的优缺点都很明显:
    • 优点:服务使用方,无需大规模调整,仅在使用时,按照新接口约定,核对输入输出的处理即可。
    • 缺点:应用网关需要理解多而繁杂的请求类型,如何兼容适配,需要有一套完整的处理规则。

两种方案对比如下:

方案仅支持一种请求类型适配各种不同请求类型
网关复杂度
使用方影响程度
迁移成本

在考虑方案时,除了技术方案本身之外,通常也会将 使用方的使用意愿以及使用成本 作为一个重要的衡量因素,一切技术都应为具体的使用场景来服务 , 脱离了具体的使用场景,为了技术而技术,无异于虚空打靶。 基于此种考虑,我们选择第二个方案,由网关适配多种多样的请求类型,复杂度由网关消化,进而简化使用这的复杂度以及接入成本。

方案设计

对于我们所选择的方案,具有明显的 多输入格式 的特征,而为了标准化后续流程,必须统一输入数据格式,这一类过程一般叫做 数据标准化处理 ,而此类场景,非常适合采用 设计模式中的适配器模式 解决问题. 采用适配器模式,我们需要完成如下几个步骤

适配器接口约束

type RequestBodyParseAdaptor interface {
    // Parse 解析Body数据,解析结果会反序列化至 receiver , 同时, 会以 map 结构返回
    Parse(ctx *gin.Context, receiver any) (map[string]any, error)
}

适配器注册

对于适配器,需要维护一个全局的注册表,用于注册支持的请求类型, 其结构为

adaptorTable = map[string]RequestBodyParseAdaptor

在服务启动之时,注册适配器

适配器触发

触发适配器,则是基于请求类型,将处理逻辑分发至不同的实际处理适配器,其逻辑如下:

// ParseRequestBody 解析请求BODY数据
func ParseRequestBody(ctx *gin.Context, receiver any) (map[string]any, error) {
    contentType := strings.ToLower(strings.ReplaceAll(ctx.ContenType(), " ", ""))
    // 裁剪出真实的类型,之所以截取,是因为 content_type 中可能还包含编码信息, 如 : application/json;chaset=utf8
    contentTypeArr := strings.Split(contentType, ";")
    contentType = contentTypeArr[0]
    if _, exist := adaptorTable[contentType]; !exist {
        return nil, errors.New(contentType + " : adaptor not found")
    }
    if parseResult, err := adaptorTable[contentType].Parse(ctx, receiver); nil != err {
        return nil, err
    } else {
        return parseResult, err
    }
}

常见请求类型实现

application/json

application/xml

application/x-www-form-urlencoded

application/yml(yaml)

扩展