惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Fortinet All Blogs
S
Secure Thoughts
月光博客
月光博客
美团技术团队
雷峰网
雷峰网
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
N
News and Events Feed by Topic
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Forbes - Security
Forbes - Security
W
WeLiveSecurity
P
Proofpoint News Feed
阮一峰的网络日志
阮一峰的网络日志
爱范儿
爱范儿
G
GRAHAM CLULEY
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
AI
AI
Last Week in AI
Last Week in AI
Google Online Security Blog
Google Online Security Blog
Schneier on Security
Schneier on Security
云风的 BLOG
云风的 BLOG
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Recent Announcements
Recent Announcements
Webroot Blog
Webroot Blog
T
Tor Project blog
Cisco Talos Blog
Cisco Talos Blog
N
News and Events Feed by Topic
罗磊的独立博客
The Register - Security
The Register - Security
Blog — PlanetScale
Blog — PlanetScale
T
Threat Research - Cisco Blogs
博客园 - 【当耐特】
Apple Machine Learning Research
Apple Machine Learning Research
人人都是产品经理
人人都是产品经理
T
The Exploit Database - CXSecurity.com
www.infosecurity-magazine.com
www.infosecurity-magazine.com
B
Blog
腾讯CDC
Microsoft Azure Blog
Microsoft Azure Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Engineering at Meta
Engineering at Meta
Latest news
Latest news
IT之家
IT之家
D
DataBreaches.Net
博客园 - 司徒正美
N
Netflix TechBlog - Medium
V
V2EX
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知

C0reFast记事本

一个virtio-blk设备IO大小的问题 使用单台高配服务器(部署你的业务) 一个Solidigm P41 Plus的冷数据问题 测试ARP/ND双发效果的小工具 硬件Bonding卸载场景下的ARP/ND双发 “无限”套娃,在WSL的Docker中使用YOLOv11做目标检测 在三条指令内实现闰年判断 一个中断关闭时间太长导致的网络延迟问题 一个Linux新老内核对于硬件中断的统计差异 IPIP隧道导致的DPDK收包RSS队列不均匀问题 在KVM虚拟机中开启TSC作为时钟源 x86平台的TSC(TIME-STAMP COUNTER) 一个UFO引发的惨案 在RHEL系统中快速抓取火焰图 DIY一个8盘位全闪NAS 分享一个记录LSI RAID卡芯片和各个OEM对应卡型号列表的神贴 在移动硬盘盒上启用SSD的Trim功能 SPDK的“Reduce”块压缩算法 实现一个Kubernetes的身份认证代理服务
反序列化AWS/阿里云样式的基于Query的API请求
陈孚 · 2023-07-13 · via C0reFast记事本

对于比较了解云计算的人来说,一定接触过AWS、阿里云的API接口,这两者的API调用方式很相似,当然具体谁参考谁这里就不深究了。以给EC2/ECS添加Tag这个接口为例:

AWS:

https://ec2.amazonaws.com/?Action=CreateTags
&ResourceId.1=ami-1a2b3c4d
&ResourceId.2=i-1234567890abcdef0
&Tag.1.Key=webserver
&Tag.1.Value=
&Tag.2.Key=stack
&Tag.2.Value=Production
&AUTHPARAMS

阿里云:

https://ecs.aliyuncs.com/?Action=TagResources
&RegionId=cn-hangzhou
&ResourceId.1=i-bp1j6qtvdm8w0z1o0****
&ResourceId.2=i-bp1j6qtvdm8w0z1oP****
&ResourceType=instance
&Tag.1.Key=TestKey
&Tag.1.Value=TestKey
&<公共请求参数>

这种样式的接口设计,其实没有什么复杂的,相对比较特殊的地方在于,如果需要传入一个数组,则需要使用类似下标一样的Tag.N.Key这种格式进行传递,这个传递方式,和已有的一些诸如google/go-querystring的传递方式都不太相同,总之是个很特殊的设计。

如果需要写一个类似的服务,使用和这两家相同的API格式的话,针对这种数组格式的请求反序列化是个挺麻烦的事,而且找了一圈也没有类似的开源项目做这个。

今天借助ChatGPT写了一个反序列化函数,专门用来实现服务端对类似形态API的反序列化,通过这个函数可以很方便的将Query反序列化成一个对应的Struct:

package main

import (
	"encoding/json"
	"fmt"
	"net/url"
	"reflect"
	"strconv"
	"strings"
)

type TagRequest struct {
	Action       string   `query:"Action"`
	RegionID     string   `query:"RegionId"`
	ResourceIds  []string `query:"ResourceId"`
	ResourceType string   `query:"ResourceType"`
	Tags         []Tag    `query:"Tag"`
}

type Tag struct {
	Key   string `query:"Key"`
	Value string `query:"Value"`
}

func Unmarshal(queryStr string, output interface{}) error {
	values, err := url.ParseQuery(queryStr)
	if err != nil {
		return err
	}

	return unmarshalData(values, output)
}

func unmarshalData(values url.Values, output interface{}) error {
	outputVal := reflect.ValueOf(output)
	if outputVal.Kind() != reflect.Ptr {
		return fmt.Errorf("output must be a pointer")
	}

	outputElem := outputVal.Elem()
	outputType := outputElem.Type()

	for i := 0; i < outputType.NumField(); i++ {
		field := outputType.Field(i)
		tag := field.Tag.Get("query")
		if tag == "" {
			continue
		}

		value := values.Get(tag)
		fieldVal := outputElem.FieldByName(field.Name)

		if field.Type.Kind() == reflect.Slice {
			elemType := field.Type.Elem()
			if elemType.Kind() != reflect.Struct {
				prefix := tag + "."
				arrIndex := 1
				for {
					currKey := prefix + fmt.Sprint(arrIndex)
					currValue := values.Get(currKey)
					if currValue == "" {
						break
					}

					currSliceVal := reflect.ValueOf(currValue)
					fieldVal.Set(reflect.Append(fieldVal, currSliceVal))
					arrIndex++
				}
			} else {
				prefix := tag + "."
				objIndex := 1
				outer := true
				for outer {
					innerValues := make(url.Values)
					for innerKey, innerValue := range values {
						if strings.HasPrefix(innerKey, prefix+strconv.Itoa(objIndex)+".") {
							innerValues.Set(strings.TrimPrefix(innerKey, prefix+strconv.Itoa(objIndex)+"."), innerValue[0])
						}
					}
					if len(innerValues) == 0 {
						break
					}

					newStructPtr := reflect.New(elemType)
					err := unmarshalData(innerValues, newStructPtr.Interface())
					if err != nil {
						return err
					}
					fieldVal.Set(reflect.Append(fieldVal, newStructPtr.Elem()))

					objIndex++
				}
			}
		} else {
			fieldVal.Set(reflect.ValueOf(value))
		}
	}

	return nil
}

func main() {
	queryStr := "?Action=TagResources&RegionId=cn-hangzhou&ResourceId.1=i-bp1j6qtvdm8w0z1o0&ResourceId.2=i-bp1j6qtvdm8w0z1oP&ResourceType=instance&Tag.1.Key=TestKey&Tag.1.Value=TestValue&Tag.2.Key=TestKey&Tag.2.Value=TestValue"

	req := TagRequest{}
	err := Unmarshal(strings.TrimLeft(queryStr, "?"), &req)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	jsonOutput, _ := json.MarshalIndent(req, "", "  ")
	fmt.Println("Unmarshaled output:", string(jsonOutput))
}

测试一下:

% go run main.go
Unmarshaled output: {
  "Action": "TagResources",
  "RegionID": "cn-hangzhou",
  "ResourceIds": [
    "i-bp1j6qtvdm8w0z1o0",
    "i-bp1j6qtvdm8w0z1oP"
  ],
  "ResourceType": "instance",
  "Tags": [
    {
      "Key": "TestKey",
      "Value": "TestValue"
    },
    {
      "Key": "TestKey",
      "Value": "TestValue"
    }
  ]
}

嗯,ChatGPT牛逼!为了方便大家使用,我创建了一个项目c0refast/aws-querystring,可以方便地作为库使用:

package main

import (
	"encoding/json"
	"fmt"
	"net/url"

	"github.com/c0refast/aws-querystring/query"
)

type TagRequest struct {
	Action       string   `query:"Action"`
	RegionID     string   `query:"RegionId"`
	ResourceIds  []string `query:"ResourceId"`
	ResourceType string   `query:"ResourceType"`
	Tags         []Tag    `query:"Tag"`
}

type Tag struct {
	Key   string `query:"Key"`
	Value string `query:"Value"`
}

func main() {
	queryStr := "Action=TagResources&RegionId=cn-hangzhou&ResourceId.1=i-bp1j6qtvdm8w0z1o0&ResourceId.2=i-bp1j6qtvdm8w0z1oP&ResourceType=instance&Tag.1.Key=TestKey&Tag.1.Value=TestValue&Tag.2.Key=TestKey&Tag.2.Value=TestValue"
	urlValues, _ := url.ParseQuery(queryStr)
	req := TagRequest{}
	err := query.BindQuery(urlValues, &req)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	jsonOutput, _ := json.MarshalIndent(req, "", "  ")
	fmt.Println("Unmarshaled output:", string(jsonOutput))
}