golang中实现通用http参数与结构体的转换

作者介绍:衡阵,2011年加入腾讯,先后经历webqq,qq互联,手Q后台等相关的工作,目前负责NOW直播的后台开发工作。热爱后台开发,喜欢研究新的技术。对Java/C++/Golang等都非常感兴趣。

最近基于golang 实现一个通用的http的协议代理,把来自http的请求转换成内部的通信协议。内部协议是基于pb的,所以关键就是实现pb和http请求中的参数的转换。

研究protoc生成的go源码发现,生成的go的结构体中已经自带的json的tag,可以很方便的在json和pb之间互转。

于是想到,可以以一个请求参数来传json来实现。

var data={name:"hello"}
var url="http://test.xx.com/cgi-bin/request?data="+urlencode(data)
http.get(url)

这样在服务端先拿到data的数据,直接用json库就可以转成相关的结构体。

这样实现虽然简单,但并不直观。对用户来说,更习惯用。

var url="http://test.xx.com/cgi-bin/request?name=hello"

这种形式来请求。由于其他语言习惯把请求参数存在一个map中,于是想golang是不是也可以这样处理。于是问题变成一个mapstringstring和json的转换的故事。搜下网上发现有人提出类似的问题,还是playgroud上的一个练习。代码如下:

func SetField(obj interface{}, name string, value interface{}) error {
    structValue := reflect.ValueOf(obj).Elem()
    structFieldValue := structValue.FieldByName(name)

    if !structFieldValue.IsValid() {
        return fmt.Errorf("No such field: %s in obj", name)
    }

    if !structFieldValue.CanSet() {
        return fmt.Errorf("Cannot set %s field value", name)
    }

    structFieldType := structFieldValue.Type()
    val := reflect.ValueOf(value)
    if structFieldType != val.Type() {
        return errors.New("Provided value type didn't match obj field type")
    }

    structFieldValue.Set(val)
    return nil
}

type MyStruct struct {
    Name string
    Age  int64
}

func (s *MyStruct) FillStruct(m map[string]interface{}) error {
    for k, v := range m {
        err := SetField(s, k, v)
        if err != nil {
            return err
        }
    }
    return nil
}

func main() {
    myData := make(map[string]interface{})
    myData["Name"] = "Tony"
    myData["Age"] = int64(23)

    result := &MyStruct{}
    err := result.FillStruct(myData)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(result)
}

这个代码实现了简单的map到struct的转换,但要求类型强一致。所以需要实现弱类型的转换。后来发现,github上已经有一个开源的实现。

https://github.com/mitchellh/mapstructure 通过很简单的代码就可以实现我们想要的功能

  paras := make(map[string]interface{})
	setfunc := func(k []byte, v []byte) {
		paras[string(k)] = string(v)
	}
	if ctx.IsPost() {
		ctx.PostArgs().VisitAll(setfunc)
	} else if ctx.IsGet() {
		ctx.QueryArgs().VisitAll(setfunc)
	}
	ex := mapstructure.WeakDecode(paras, pb)

到了这里,大部分场景都可以实现了。但有些请求是有消息嵌套的,虽然mapstructure是支持嵌套转换的,但我们的请求参数只是一层的mapstringstring。

这种情况mapstructure无能为力了。看下mapstructure的源码,逻辑比较简单,既然你不支持,就改到你支持。我们定义如果有结构体嵌套,二级参数要是一个json字符串。在处理结构提的地方,如果发现传入的是个字符串,就尝试用json去处理一下,然后再走后面的逻辑。

在slice的地方也同样处理

这样处理完了之后试了一下,果然处理嵌套的结构体了。但是在实际使用的时候发现,有人竟然在pb中定义普通的字符串为bytes,这样在生成的go代码中就是[]byte类型。这种情况很不巧也会走到decodeSlice的逻辑,而我们并没有考虑兼容。事实上mapstructure这个框架就没有考虑这种情形。按json定义的标准,[]byte类型要以base64编码,我们也遵循这种规范。于是加上这个代码:

至此,这个转换基本完美。但是发现一使用,发现还是有坑存在,对应proto文件中定义的带下划线的字段,生成的struct成员代码是驼峰型的。标准库中的json可以通过反射拿到tag中的原始名称正常的输出。但我们用mapstructure默认是以字段名来解析的。

本来以为要自己处理一下,在修改一下mapstructure的源码,然而阅读代码的时候发现,mapstructure支持指定要处理的tag。并且tag处理逻辑是兼容pb生成的json tag的。我们只要在解析时指定一下tag即可。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

衡阵的专栏

1 篇文章1 人订阅

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏漏斗社区

CTF| SQL注入之获取数据类

上周发了一篇 SQL注入登录类的题型文章分析,这种题目一般是绕过登录限制。常规的SQL注入题需要我们一步步注入出数据,大部分题目需要我们有一定代码审计的能力,分...

3887
来自专栏撸码那些事

我看依赖注入

1403
来自专栏为数不多的Android技巧

请不要滥用SharedPreference

SharedPreference是Android上一种非常易用的轻量级存储方式,由于其API及其友好,得到了很多很多开发者的青睐。但是,SharedPrefer...

1344
来自专栏coding for love

JS常用设计模式解析02-策略模式

在于都本文之前,希望大家能够先阅读以下JS进阶系列03-JS面向对象的三大特征之多态这篇文章,了解JS的多态。在这篇文章,我们举了一个例子,就是选拔官员选拔合唱...

883
来自专栏Python自动化测试

工厂设计模式在自动化中的引用(一)

在自动化测试的范围中,目前依据webdriver的,web应用测试框架有selenium2,对于移动app自动化的测试,有appium,selen...

873
来自专栏小程序·云开发专栏

小程序·云开发的云函数路由高级玩法

在掘金开发者大会上,在推荐实践那里,我有提到一种云函数的用法,我们可以将相同的一些操作。

8.4K12
来自专栏C语言C++游戏编程

抖音很火的告白编程程序,C语言一样也能做

最近抖音上火了一个由小伙伴自己制作的一个表白代码,很多小伙伴都在问这个表白代码是怎么写的?大家都知道是使用vbs实现的!虽说小编也承让VBS实现更简单,但是如果...

5172
来自专栏农夫安全

高级PHP应用程序漏洞审核技术【一】

前言 小编入门代码审计时看的几篇写的比较经典的PDF文档之一,分享出来希望能帮助到想学习代码审计的小伙伴。 [目录] 1. 前言 2. 传统的代码审计技...

35011
来自专栏轮子工厂

JVM知识点总览:高级Java工程师面试必备

程序员,就是“一人,一键,二机”行走其间的孤独剑客。我们游走代码江湖,弹指间,便可掀起一场风雨变革。而在江湖中狂荡,必然要练就绝世武功,则需要内外兼备:精妙的招...

661
来自专栏SDNLAB

Ryu的一些设计方法解读

作为一个业余研究Ryu的软件工程师,一直惊叹于Ryu设计的优雅与简洁。一年多坚持下来,也有自己的一些收获,写出来和大家分享一下。 我们的故事从@set_ev_c...

2996

扫码关注云+社区