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