awsease

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 10, 2026 License: MIT Imports: 17 Imported by: 0

README

aws-ease

对 HTTP / AWS Lambda / AWS SQS 的统一、便捷封装(当前版本 v0.4.0)。心智模型只有一句话:

给一个 backend://target 地址和一段 payloadInvoke 就把它打到对应后端,返回 (body, err)

resp, err := awsease.Invoke(ctx, "https://api.internal/v1/orders", []byte(`{"sku":"A1"}`))
go get github.com/aura-studio/aws-ease

可运行示例见 examples/go run ./examples/localdev 开箱即跑); doc/plan.md 是 v0.2 时期的历史设计规格,现行 API 以本 README 与代码为准。

解决什么问题

lambda.Invokesqs.SendMessage 这类调用各有一套 SDK、构造、错误处理样板; 「调的是 HTTP 还是 Lambda 还是 SQS」又常硬编码进业务逻辑,切环境就得改代码。

aws-ease 把「调什么」和「怎么调」用一个地址串统一起来:scheme 即后端, 特性参数(Lambda 异步、SQS FIFO/属性)全部以 query 参数写在地址里,一眼可读、可整体进配置(HTTP 后端无特性参数)。

调用方 ──► Invoke(ctx, "backend://target?特性参数", payload) ──► scheme 决定后端
                                                                 ├─ http(s):// → net/http 请求
                                                                 ├─ lambda://  → lambda.Invoke
                                                                 └─ sqs://     → SendMessage

返回值只有 (body []byte, err error),错误约定也只有一条:err 非 nil 即本次调用失败,此时 body 恒为 nil。 传输失败、地址非法、HTTP 非 2xx、Lambda 函数内部错误,全部走 err—— 三种后端只需一套 if err != nil 处理,重试/日志等横切关注点可以统一封装在 Invoke 之上。

快速开始

ctx := context.Background()

// 80% 场景:包级 Invoke(默认超时、默认 AWS 凭证链;惰性初始化、并发安全)
body, err := awsease.Invoke(ctx, "https://api.internal/v1/users/42", nil) // HTTP 恒为 POST
body, err = awsease.Invoke(ctx, "lambda://order-create", []byte(`{"sku":"A1"}`))
_, err = awsease.Invoke(ctx, "sqs://order-events", []byte(`{"event":"created"}`)) // 推送成功 body 为 nil

// 需要注入配置(AWS config、mock、超时、本地重定向)时构造 Client,调用形态不变
c := awsease.New(awsease.WithAWSConfig(cfg))
body, err = c.Invoke(ctx, "lambda://order-create", []byte(`{"sku":"A1"}`))

地址约定

地址 路由到 说明
http://host/path?x=1https://host/path HTTP 整串原样作请求 URL(含 query);方法恒为 POST,无特性参数
lambda://<fn>[?async=true] Lambda <fn> 是函数名/ARN;payload 原样透传(无信封
lambda://<fn>/<path>[?async=true] Lambda(tunnel) 对端是 lambda 框架 reqresp 模式的函数:path in-band、包/拆传输信封、in-band 错误走 err
sqs://<queue>[?group=g&dedup=d&attr.k=v] SQS <queue> 是队列名(惰性解析+缓存)或完整队列 URL

scheme 大小写不敏感(RFC 3986):HTTPS://…Lambda://… 均合法; HTTP URL 的 scheme 发出前会统一改写为小写(http.Transport 只认小写),URL 其余部分不动。

HTTP —— http(s)://…

整串就是请求 URL,path/query 都写在里面,字节原样发出——不重排、不重编码 (; 分隔、预编码值、裸键全部原样透传),预签名 URL 等对字节敏感的地址可以放心用。 方法恒为 POST,无特性参数(不再有 ease. 保留字,不支持自定义方法或请求头)。

返回与错误:2xx 返回响应体字节;非 2xx 一律返回 err(错误串含 status <码> 与响应体,方便直接打日志),body 恒为 nil。

body, err := awsease.Invoke(ctx,
    "https://api.internal/v1/users/42?pretty=1", []byte(`{"name":"new"}`))
Lambda —— lambda://<fn>[?async=true]

<fn> 是函数名或 ARN;? 后整段都是特性参数。第一个 / 之前是函数名,之后是 tunnel 路径(见下节);函数名为空、或带 / 但路径为空(lambda://fn/)视为非法 (ErrBadTarget,及早报错而非让 AWS 报隐晦错)。 不带路径时 payload 原样作 InvokeInput.Payload无信封——调用方传什么,Lambda 收什么。

特性参数 作用
async=true(或 1 InvocationType=Event 即发即忘

返回与错误:

  • 同步成功:返回 Lambda 返回的 payload 字节。
  • 函数内部报错(FunctionError 非空):err(错误串含函数错误名与错误 payload),body 恒为 nil—— 不伪造假状态码,也不让失败静默成功。
  • 异步成功:(nil, nil)——仅表示已被 AWS 接受投递,不代表函数已成功执行。
body, err := awsease.Invoke(ctx, "lambda://order-create", []byte(`{"sku":"A1","qty":2}`))
_, err = awsease.Invoke(ctx, "lambda://audit-logger?async=true", payload) // 即发即忘
Lambda tunnel —— lambda://<fn>/<path>[?async=true]

对端不是"一个函数一个语义"的 raw 函数、而是 lambda 框架 reqresp 模式的函数 (一个 Lambda 内按 in-band path 路由 N 个方法,如 scp-lambda 系列)时,路径写进 url: 库代为包/拆 reqresp 传输信封,信封内是裸业务数据

请求:payload{"path":"/<path>","payload":"<base64>"}InvokeInput.Payload。 响应:拆 {"payload":"<base64>","error":"..."}——error 非空走 err(in-band 错误不再静默,框架 404 与 service 层错误都从这里浮出),成功返回解码后的 payload 字节;响应不是信封 JSON(对端不是 reqresp 函数)报 ErrBadResponse

service 应用信封({"meta":{...},"data":"<base64>"})是对端引擎与 tunnel 之间的 内部契约:引擎收到请求后自行包、返回前自行拆,service 层错误(Meta["Error"]) 也由引擎翻译进 Response.error——客户端不感知、也绝不能代包(会双重包裹)。

路径与特性参数校验从严(及早报错):路径空段(fn//xfn/x/)非法;特性参数 只认 async=true|1|false|0,未知键/值一律 ErrBadTarget——特性参数决定线上格式, 拼写错误必须显式失败而非静默改变语义。

特性参数 作用
async=true(或 1 即发即忘(请求仍包信封;in-band 错误天然不可见)
// reqresp tunnel:path in-band,in-band 错误可见;payload 即方法的请求 JSON
body, err := awsease.Invoke(ctx,
    "lambda://scp-lambda-tango/api/tango/v1/upload",
    []byte(`{"line":"..."}`))
SQS —— sqs://<queue>[?group=g&dedup=d&attr.k=v]

<queue> 是队列名(惰性 GetQueueUrl 解析并带锁缓存,只解析一次)或完整队列 URL(以 http 开头视为已是 URL 直用); ? 后整段都是特性参数。

特性参数 作用
group=<g> FIFO MessageGroupId
dedup=<d> FIFO MessageDeduplicationId
attr.<key>=<val> String 类型 MessageAttributes,可多个;键名原样保留,不被归一化改写

返回与错误:

  • 发送成功:(nil, nil)——推送语义,MessageId 等回执信息不返回(要它通常也没用,不返回多余信息)。
  • payload 非合法 UTF-8:errerrors.Is(err, ErrBadTarget))。这是 AWS 对 MessageBody 的硬限制, 库显式校验并报明确错误,而非假装裸字节透传、让 AWS 端报隐晦错。
_, err := awsease.Invoke(ctx,
    "sqs://order-events.fifo?group=orders&dedup=order-7&attr.type=order",
    []byte(`{"event":"created","id":7}`))

错误约定

只有一条规则:err 非 nil 即失败,此时 body 恒为 nil(不返回半成品,消除「err 非 nil 时 body 还能不能用」的歧义)。

失败场景 表现
地址非法(空串/缺 scheme/lambda 函数名含 / errerrors.Is(err, ErrBadTarget)
未知 scheme errerrors.Is(err, ErrUnknownScheme)
网络 / AWS SDK / 超时 / context 取消 err(包装底层错误,可 errors.Is 透传判别)
HTTP 非 2xx err,错误串含 status <码> 与响应体
Lambda FunctionError 非空 err,错误串含函数错误名与错误 payload
SQS payload 非 UTF-8 errerrors.Is(err, ErrBadTarget)
if _, err := awsease.Invoke(ctx, target, payload); errors.Is(err, awsease.ErrUnknownScheme) {
    // scheme 不是 http/https/lambda/sqs
}

Client 与 Options

包级 Invoke 用包级默认客户端(默认超时 30s、默认 AWS 凭证链,惰性初始化、并发安全)。 需要注入基础设施时用 New(opts...) 构造 Client,再调 Client.Invoke(签名与包级完全一致):

c := awsease.New(
    awsease.WithAWSConfig(cfg),
    awsease.WithTimeout(10*time.Second),
)
body, err := c.Invoke(ctx, "lambda://order-create", payload)
Option 作用
WithHTTPClient(h) 替换底层 *http.Client(transport / 代理 / mTLS / 连接池)。Timeout 应留零,超时交给 context 或 WithTimeout,避免两个超时源打架
WithAWSConfig(cfg) 注入已加载的 aws.Config,共享给 lambda + sqs(生产最常用,一次加载)。不传则首次需要时惰性 LoadDefaultConfig
WithLambdaAPI(l) 注入 Lambda 客户端实现(测试 mock),签名即 aws-sdk-go-v2 原生形状
WithSQSAPI(s) 注入 SQS 客户端实现(测试 mock)
WithAWSEndpoint(url) 为 lambda/sqs 设置 base endpoint(指向 LocalStack 等自建端点)
WithTimeout(d) 每次调用的默认超时(默认 30s,内部 context.WithTimeout;调用方更短的 deadline 自动优先)。传 0 或负值禁用库级超时,完全交给调用方的 ctx(如 Lambda 同步长任务)
WithLocalRedirect(base) lambda://sqs:// 重定向到 base 的 HTTP mock(本地联调,见下节)

New() 无参即可用:只调 HTTP 时不触发任何 AWS 凭证读取(lambda/sqs 客户端惰性加载)。 Client 并发安全(含 SQS QueueUrl 缓存的内部加锁),构造一次、全程复用。

本地联调

业务地址串一字不改,构造时加一行把 lambda://sqs:// 重定向到本地 HTTP mock:

c := awsease.New(awsease.WithLocalRedirect("http://localhost:8080"))
// c.Invoke(ctx, "lambda://order-create", payload) 实际打到 http://localhost:8080/lambda/order-create
// c.Invoke(ctx, "sqs://order-events", payload)    实际打到 http://localhost:8080/sqs/order-events
// http(s):// 地址不受影响

重定向语义(与生产对齐,本地验证过的行为切回真实 AWS 不变):

  • 特性参数原样转为重定向 URL 的 query(按键排序),供 mock 端观察;被解读为 HTTP 特性参数—— 例如 sqs://q?method=DELETE 里的 method 只是透传给 mock 的普通参数,不会改写实际 HTTP 方法 (重定向请求仍按惯例:payload 非空 POST、为空 GET)。
  • 返回值与生产一致:sqs://lambda://…?async=true|1 成功返回 (nil, nil),不返回 mock 的响应体; lambda 同步调用返回 mock 的响应体。
  • 校验与生产一致:sqs 在重定向路径同样校验 payload 必须是合法 UTF-8(失败 ErrBadTarget)。

启动 mock(/lambda//sqs/ 两个 echo 路由,与重定向约定一一对应):

go run ./cmd/aws-ease-mock

测试

所有测试集中在 tests/,一律黑盒(只测公开 API,用注入 fake + httptest,不碰真实 AWS):

go test ./tests/
集成测试(真实 AWS,opt-in)

tests/integration_test.go 用 build tag integration 隔离,默认 go test/CI 不会编译运行。 需要真实 AWS 凭证(环境变量或 ~/.aws),会自建并清理临时资源(SQS 队列 / Lambda 函数):

# HTTP(checkip.amazonaws.com)+ SQS(建队列→发送→收回→删队列)+ Lambda 错误路径
go test -tags integration -run Integration -v ./tests/

# 额外做一次真实 Lambda 成功调用(建一次性 echo 函数→invoke→删除,复用现有执行角色)
AWS_EASE_IT_LAMBDA_CREATE=1 go test -tags integration -run Integration -v ./tests/

建议用最小权限 IAM 用户(临时 SQS 队列 + lambda:InvokeFunctionAWS_EASE_IT_LAMBDA_CREATE 另需 lambda:CreateFunction/DeleteFunction 等),不要用 root key。

License

MIT

Documentation

Overview

Package awsease 提供对 HTTP / AWS Lambda / AWS SQS 的统一、便捷封装。

心智模型只有一行:「Invoke(ctx, url, payload) -> (body, err)」。

  • url 是 backend://target 字符串,scheme 决定后端(http/https/lambda/sqs); 特性参数(Lambda 异步、SQS 属性/FIFO)以 query 参数写在 url 里;HTTP 后端无特性参数。
  • payload 与返回值都只是有效数据本身,默认原样透传,库不包任何信封。 唯一例外是 lambda tunnel 模式(lambda://<fn>/<path>):对端是 lambda 框架 reqresp 模式的函数时,路径必须 in-band,库代为包/拆 reqresp 传输信封 ——信封内仍是裸业务数据(service 应用信封由对端引擎自行包拆,客户端不感知)。
  • 一切失败(传输失败、HTTP 非 2xx、Lambda 函数内部错误、tunnel in-band 错误) 都通过 err 表达,不返回多余信息。

主入口是包级 Invoke;需要注入配置(AWS config、mock、超时、本地重定向)时 用 New(opts...) 构造 Client 再调 Client.Invoke。

Index

Constants

View Source
const Version = "1.0.0"

Version 是当前模块语义化版本号。

Variables

View Source
var (
	// ErrBadTarget 表示地址为空 / 缺 scheme / lambda 函数名非法 / SQS 消息体非 UTF-8 等
	// —— 调用方一侧的地址/入参问题,重试同样的调用必然同样失败。
	ErrBadTarget = errors.New("awsease: invalid target")
	// ErrUnknownScheme 表示 scheme 不是 http/https/lambda/sqs。
	ErrUnknownScheme = errors.New("awsease: unknown scheme")
	// ErrBadResponse 表示 tunnel 模式下对端响应不是合法的 reqresp 信封
	// —— 对端一侧的问题(函数不是 reqresp 模式、或响应被截断/损坏),与地址合法性无关。
	ErrBadResponse = errors.New("awsease: invalid tunnel response")
)

Functions

func Invoke added in v0.4.0

func Invoke(ctx context.Context, url string, payload []byte) ([]byte, error)

Invoke 用包级默认客户端执行一次调用(默认超时、默认 AWS 凭证链,惰性初始化、并发安全)。 需要细控基础设施时用 New(opts...).Invoke。

url 形如 backend://target[?特性参数]:

http/https:整串即请求 URL(含 query,原样透传);方法恒为 POST,无特性参数。
lambda://<fn>[?async=true|1]:fn 是函数名或 ARN;async=true(或 1)即发即忘(返回 body 为 nil)。
lambda://<fn>/<path>[?async=true|1]:tunnel 模式——对端是 lambda 框架 reqresp
    模式的函数:裸业务 payload 包进 {"path":"/<path>","payload":"<base64>"}
    传输信封,响应拆信封并把 in-band 错误(Response.error,含框架 404 与
    service 层错误)翻译成 err。service 应用信封由对端引擎自行包拆,客户端不感知。
sqs://<queue|queue-url>[?group=g&dedup=d&attr.k=v]:group/dedup 是 FIFO 字段,
    attr.<key>=<value> 映射为 String 类型 MessageAttributes(可多个)。发送成功返回 body 为 nil。

错误约定:err 非 nil 即本次调用失败(地址非法、传输失败、HTTP 非 2xx、Lambda 函数内部错误、 tunnel in-band 错误、tunnel 响应非信封 ErrBadResponse),此时 body 恒为 nil; err 为 nil 时 body 是后端返回的有效数据(HTTP 响应体 / Lambda 返回 payload; tunnel 模式下为拆完信封的业务数据)。

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client 是唯一门面,并发安全(含 SQS QueueUrl 缓存的内部加锁)。零值不可用,必须经 New 构造。

func New

func New(opts ...Option) *Client

New 创建客户端。无参即可用(HTTP 立即可用;lambda/sqs 的真实 AWS 客户端惰性加载)。

func (*Client) Invoke

func (c *Client) Invoke(ctx context.Context, target string, payload []byte) ([]byte, error)

Invoke 解析 url 的 scheme -> 选后端 -> 执行 -> 返回后端的有效数据。 语义与包级 Invoke 完全一致(见其文档),只是走本 Client 的配置。

type LambdaAPI added in v0.4.0

type LambdaAPI interface {
	Invoke(ctx context.Context, in *awslambda.InvokeInput, optFns ...func(*awslambda.Options)) (*awslambda.InvokeOutput, error)
}

LambdaAPI 是 doLambda 依赖的最小 Lambda 客户端接口(签名即 aws-sdk-go-v2 原生形状,便于 mock)。

type Option added in v0.4.0

type Option func(*config)

Option 配置 Client(构造期、基础设施级,构造一次)。

func WithAWSConfig added in v0.4.0

func WithAWSConfig(cfg awssdk.Config) Option

WithAWSConfig 注入已加载的 aws.Config,共享给 lambda + sqs(生产最常用,一次加载)。

func WithAWSEndpoint added in v0.4.0

func WithAWSEndpoint(url string) Option

WithAWSEndpoint 为 lambda/sqs 设置 base endpoint(指向 LocalStack 等自建端点)。

func WithHTTPClient added in v0.4.0

func WithHTTPClient(h *http.Client) Option

WithHTTPClient 替换底层 *http.Client(transport / 代理 / mTLS / 连接池)。 注意:超时请用 context 或 WithTimeout,注入的 http.Client.Timeout 应留零,避免与 context 超时打架。

func WithLambdaAPI added in v0.4.0

func WithLambdaAPI(l LambdaAPI) Option

WithLambdaAPI 注入 Lambda 客户端实现(测试 mock)。

func WithLocalRedirect added in v0.4.0

func WithLocalRedirect(base string) Option

WithLocalRedirect 把所有 lambda://、sqs:// 调用改写为对 base 的 HTTP mock 请求(本地切换,可选): lambda://<fn> -> {base}/lambda/<fn>,lambda://<fn>/<path> -> {base}/lambda/<fn>/<path> (请求体即真实 InvokeInput.Payload:tunnel 模式下是信封字节,同步响应同样拆信封), sqs://<q> -> {base}/sqs/<q>,特性参数原样转为重定向 URL 的 query 供 mock 观察。 返回值与校验语义和真实后端对齐(sqs/异步成功返回 nil body、sqs 仍校验 UTF-8), 本地联调验证过的行为切回真实 AWS 不变。

func WithSQSAPI added in v0.4.0

func WithSQSAPI(s SQSAPI) Option

WithSQSAPI 注入 SQS 客户端实现(测试 mock)。

func WithTimeout added in v0.4.0

func WithTimeout(d time.Duration) Option

WithTimeout 设置每次调用的默认超时(内部以 context.WithTimeout 套在传入 ctx 上)。默认 30s。 传 0 或负值表示禁用库级超时、完全交给调用方的 ctx(如 Lambda 同步最长可跑 15 分钟的场景)。

type SQSAPI added in v0.4.0

type SQSAPI interface {
	SendMessage(ctx context.Context, in *awssqs.SendMessageInput, optFns ...func(*awssqs.Options)) (*awssqs.SendMessageOutput, error)
	GetQueueUrl(ctx context.Context, in *awssqs.GetQueueUrlInput, optFns ...func(*awssqs.Options)) (*awssqs.GetQueueUrlOutput, error)
}

SQSAPI 是 doSQS 依赖的最小 SQS 客户端接口(签名即 aws-sdk-go-v2 原生形状,便于 mock)。

Directories

Path Synopsis
cmd
aws-ease-mock command
Command aws-ease-mock 是一个最小的本地 HTTP mock,用于配合 awsease.WithLocalRedirect 联调。
Command aws-ease-mock 是一个最小的本地 HTTP mock,用于配合 awsease.WithLocalRedirect 联调。
examples
http command
Command http 演示用 aws-ease 调普通 HTTP 后端。
Command http 演示用 aws-ease 调普通 HTTP 后端。
lambda command
Command lambda 演示用 aws-ease 调 AWS Lambda。
Command lambda 演示用 aws-ease 调 AWS Lambda。
localdev command
Command localdev 演示本地开发:用 WithLocalRedirect 把 lambda:// 与 sqs:// 打到一个 进程内的 HTTP mock,业务地址串一字不改、完全不碰真实 AWS。
Command localdev 演示本地开发:用 WithLocalRedirect 把 lambda:// 与 sqs:// 打到一个 进程内的 HTTP mock,业务地址串一字不改、完全不碰真实 AWS。
sqs command
Command sqs 演示用 aws-ease 推送 SQS 消息。
Command sqs 演示用 aws-ease 推送 SQS 消息。
internal
mock
Package mock 提供 aws-ease 本地联调用的 HTTP mock handler,配合 awsease.WithLocalRedirect 使用。
Package mock 提供 aws-ease 本地联调用的 HTTP mock handler,配合 awsease.WithLocalRedirect 使用。
Package tests 是 aws-ease 的黑盒测试套件:只通过公开 API(Invoke + Option) 验证行为,不依赖任何非导出符号。
Package tests 是 aws-ease 的黑盒测试套件:只通过公开 API(Invoke + Option) 验证行为,不依赖任何非导出符号。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL