Skip to content

fix request pass-through aws channels can't test - #2693

Merged
seefs001 merged 1 commit into
QuantumNous:mainfrom
daggeryu:main
Jan 25, 2026
Merged

fix request pass-through aws channels can't test#2693
seefs001 merged 1 commit into
QuantumNous:mainfrom
daggeryu:main

Conversation

@daggeryu

@daggeryu daggeryu commented Jan 20, 2026

Copy link
Copy Markdown
Contributor

common.GetRequestBody(c) read body is null

Summary by CodeRabbit

  • Tests
    • Improved request body handling in test scenarios to better isolate test data.

✏️ Tip: You can customize this high-level summary in your review settings.

common.GetRequestBody(c) read bod is null
@coderabbitai

coderabbitai Bot commented Jan 20, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Test modification in channel-test.go that decouples the request body instance used by the HTTP context from the body passed to the adaptor. A new buffer is created for the HTTP context while the original buffer is passed separately to DoRequest.

Changes

Cohort / File(s) Summary
Test Buffer Handling
controller/channel-test.go
Modified request body assignment to create a separate buffer instance for the HTTP context (io.NopCloser(bytes.NewBuffer(jsonData))) while passing the original requestBody to DoRequest, decoupling the two buffer references

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Suggested reviewers

  • Calcium-Ion

Poem

🐰 A buffer split in two, oh what a sight,
One for the context, one for the adaptor's flight,
No more shared states causing confusion and strife,
Independence blooms—order returns to the test life! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title refers to fixing request pass-through for AWS channels and testing issues, which aligns with the changeset that decouples request body handling to fix body reading problems.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@seefs001

Copy link
Copy Markdown
Collaborator

@coderabbitai 帮我分析一下改的这几行上下,可能是我太菜了没看懂,感觉啥也没改呢

@coderabbitai

coderabbitai Bot commented Jan 25, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the channel-test.go file to see the context around the change
cat controller/channel-test.go

Length of output: 20114


@seefs001 我来帮你详细分析一下这个改动,虽然看起来改动很小,但实际上解决了一个关键问题。

问题根源

这是一个关于 Reader 被重复读取导致数据丢失 的问题。

修改前的代码:

requestBody := bytes.NewBuffer(jsonData)
c.Request.Body = io.NopCloser(requestBody)  // 使用同一个 buffer 实例
resp, err := adaptor.DoRequest(c, info, requestBody)  // 传递同一个 buffer

修改后的代码:

requestBody := bytes.NewBuffer(jsonData)
c.Request.Body = io.NopCloser(bytes.NewBuffer(jsonData))  // 创建新的 buffer 实例
resp, err := adaptor.DoRequest(c, info, requestBody)  // 传递原来的 buffer

为什么会出问题?

bytes.Buffer 作为一个 Reader,有一个内部的读取指针。当你读取数据时:

  1. 指针会向前移动
  2. 读到末尾后,再次读取会返回 EOF(空内容)
  3. 同一个 Buffer 实例只能被完整读取一次

修改前的流程:

1. 创建 requestBody 缓冲区(包含 jsonData)
2. c.Request.Body = 包装这个缓冲区  ← 注意:是同一个实例!
3. 在某些 AWS 渠道的处理中,可能会调用 common.GetRequestBody(c)
4. GetRequestBody(c) 读取 c.Request.Body,把缓冲区读完了
5. 缓冲区的读取指针已经到末尾
6. 调用 adaptor.DoRequest(c, info, requestBody)
7. DoRequest 尝试读取 requestBody → 得到空内容!❌

修改后如何解决?

现在创建了 两个独立的缓冲区,虽然内容相同,但互不干扰:

1. requestBody = 缓冲区 A(包含 jsonData)
2. c.Request.Body = 缓冲区 B(包含 jsonData 的副本)
3. common.GetRequestBody(c) 读取缓冲区 B ✓
4. adaptor.DoRequest 读取缓冲区 A ✓
5. 两个缓冲区独立,互不影响!

实际场景

PR 标题提到的 "AWS 渠道透传测试失败" 就是这个原因:

  • AWS 的某些处理逻辑需要读取 c.Request.Body 来获取请求体
  • 但因为和 requestBody 共享同一个实例,读取后就被清空了
  • 导致实际发送请求时,body 为空,测试失败

所以虽然改动只有一行,但解决了一个很隐蔽的 bug!这种问题在 Go 中处理 io.Reader 时很常见。

希望这样解释清楚了!👍

@daggeryu daggeryu closed this Jan 25, 2026
@seefs001

Copy link
Copy Markdown
Collaborator

Tip

For best results, initiate chat on the files or code changes.
🧩 Analysis chain
@seefs001 我来帮你详细分析一下这个改动,虽然看起来改动很小,但实际上解决了一个关键问题。

问题根源

这是一个关于 Reader 被重复读取导致数据丢失 的问题。

修改前的代码:

requestBody := bytes.NewBuffer(jsonData)
c.Request.Body = io.NopCloser(requestBody)  // 使用同一个 buffer 实例
resp, err := adaptor.DoRequest(c, info, requestBody)  // 传递同一个 buffer

修改后的代码:

requestBody := bytes.NewBuffer(jsonData)
c.Request.Body = io.NopCloser(bytes.NewBuffer(jsonData))  // 创建新的 buffer 实例
resp, err := adaptor.DoRequest(c, info, requestBody)  // 传递原来的 buffer

为什么会出问题?

bytes.Buffer 作为一个 Reader,有一个内部的读取指针。当你读取数据时:

  1. 指针会向前移动
  2. 读到末尾后,再次读取会返回 EOF(空内容)
  3. 同一个 Buffer 实例只能被完整读取一次

修改前的流程:

1. 创建 requestBody 缓冲区(包含 jsonData)
2. c.Request.Body = 包装这个缓冲区  ← 注意:是同一个实例!
3. 在某些 AWS 渠道的处理中,可能会调用 common.GetRequestBody(c)
4. GetRequestBody(c) 读取 c.Request.Body,把缓冲区读完了
5. 缓冲区的读取指针已经到末尾
6. 调用 adaptor.DoRequest(c, info, requestBody)
7. DoRequest 尝试读取 requestBody → 得到空内容!❌

修改后如何解决?

现在创建了 两个独立的缓冲区,虽然内容相同,但互不干扰:

1. requestBody = 缓冲区 A(包含 jsonData)
2. c.Request.Body = 缓冲区 B(包含 jsonData 的副本)
3. common.GetRequestBody(c) 读取缓冲区 B ✓
4. adaptor.DoRequest 读取缓冲区 A ✓
5. 两个缓冲区独立,互不影响!

实际场景

PR 标题提到的 "AWS 渠道透传测试失败" 就是这个原因:

  • AWS 的某些处理逻辑需要读取 c.Request.Body 来获取请求体
  • 但因为和 requestBody 共享同一个实例,读取后就被清空了
  • 导致实际发送请求时,body 为空,测试失败

所以虽然改动只有一行,但解决了一个很隐蔽的 bug!这种问题在 Go 中处理 io.Reader 时很常见。

希望这样解释清楚了!👍

这里aws测试的时候有用到c.Request.Body吗?测试流程里是直接用的requestBody还是用的c.Request

@seefs001

Copy link
Copy Markdown
Collaborator

Tip
For best results, initiate chat on the files or code changes.
🧩 Analysis chain
@seefs001 我来帮你详细分析一下这个改动,虽然看起来改动很小,但实际上解决了一个关键问题。

问题根源

这是一个关于 Reader 被重复读取导致数据丢失 的问题。

修改前的代码:

requestBody := bytes.NewBuffer(jsonData)
c.Request.Body = io.NopCloser(requestBody)  // 使用同一个 buffer 实例
resp, err := adaptor.DoRequest(c, info, requestBody)  // 传递同一个 buffer

修改后的代码:

requestBody := bytes.NewBuffer(jsonData)
c.Request.Body = io.NopCloser(bytes.NewBuffer(jsonData))  // 创建新的 buffer 实例
resp, err := adaptor.DoRequest(c, info, requestBody)  // 传递原来的 buffer

为什么会出问题?

bytes.Buffer 作为一个 Reader,有一个内部的读取指针。当你读取数据时:

  1. 指针会向前移动
  2. 读到末尾后,再次读取会返回 EOF(空内容)
  3. 同一个 Buffer 实例只能被完整读取一次

修改前的流程:

1. 创建 requestBody 缓冲区(包含 jsonData)
2. c.Request.Body = 包装这个缓冲区  ← 注意:是同一个实例!
3. 在某些 AWS 渠道的处理中,可能会调用 common.GetRequestBody(c)
4. GetRequestBody(c) 读取 c.Request.Body,把缓冲区读完了
5. 缓冲区的读取指针已经到末尾
6. 调用 adaptor.DoRequest(c, info, requestBody)
7. DoRequest 尝试读取 requestBody → 得到空内容!❌

修改后如何解决?

现在创建了 两个独立的缓冲区,虽然内容相同,但互不干扰:

1. requestBody = 缓冲区 A(包含 jsonData)
2. c.Request.Body = 缓冲区 B(包含 jsonData 的副本)
3. common.GetRequestBody(c) 读取缓冲区 B ✓
4. adaptor.DoRequest 读取缓冲区 A ✓
5. 两个缓冲区独立,互不影响!

实际场景

PR 标题提到的 "AWS 渠道透传测试失败" 就是这个原因:

  • AWS 的某些处理逻辑需要读取 c.Request.Body 来获取请求体
  • 但因为和 requestBody 共享同一个实例,读取后就被清空了
  • 导致实际发送请求时,body 为空,测试失败

所以虽然改动只有一行,但解决了一个很隐蔽的 bug!这种问题在 Go 中处理 io.Reader 时很常见。
希望这样解释清楚了!👍

这里aws测试的时候有用到c.Request.Body吗?测试流程里是直接用的requestBody还是用的c.Request

@coderabbitai

@coderabbitai

coderabbitai Bot commented Jan 25, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@seefs001 好问题!让我深入查看一下实际的代码流程,看看到底是用的哪个。


🏁 Script executed:

#!/bin/bash
# 1. 查看 DoRequest 方法的实现,看它如何使用 c 和 requestBody 参数
ast-grep --pattern 'func ($$$) DoRequest($_, $_, $_) ($$$) {
  $$$
}'

# 2. 搜索 common.GetRequestBody 的使用位置,特别是在 AWS 相关的代码中
rg 'common\.GetRequestBody' -n -C 3

# 3. 查看 AWS adaptor 的具体实现
fd -e go aws | head -20

# 4. 搜索 AWS 渠道相关的 adaptor 实现
rg -n 'type.*Adaptor.*struct' --type go -A 5 | grep -i aws -B 5 -A 5

Length of output: 22860


完美!我找到了关键证据。让我查看一下完整的流程:


🏁 Script executed:

#!/bin/bash
# 查看 doAwsClientRequest 的完整实现
ast-grep --pattern 'func doAwsClientRequest($$$) {
  $$$
}'

# 或者用 rg 搜索这个函数的实现
rg -n 'func doAwsClientRequest' -A 50 relay/channel/aws/relay-aws.go

Length of output: 1940


🏁 Script executed:

#!/bin/bash
# 查看 formatRequest 函数,看它是否调用 buildAwsRequestBody
rg -n 'func formatRequest' -A 30 relay/channel/aws/relay-aws.go

# 同时查看 buildAwsRequestBody 在哪里被调用
rg -n 'buildAwsRequestBody' relay/channel/aws/

Length of output: 478


🏁 Script executed:

#!/bin/bash
# 查看 formatRequest 的完整实现
rg -n 'func formatRequest' -A 10 relay/channel/aws/relay-aws.go

# 查看 doAwsClientRequest 中 formatRequest 的调用,特别注意它的参数
rg -n 'formatRequest\(' relay/channel/aws/relay-aws.go -B 2 -A 2

Length of output: 236


🏁 Script executed:

#!/bin/bash
# 查看完整的 formatRequest 函数实现,看它如何处理 requestBody
sed -n '/^func formatRequest/,/^func /p' relay/channel/aws/relay-aws.go | head -n -1

# 也查看 buildAwsRequestBody 的完整实现
sed -n '/^func buildAwsRequestBody/,/^}/p' relay/channel/aws/relay-aws.go

Length of output: 644


🏁 Script executed:

#!/bin/bash
# 查看 formatRequest 的完整实现
cat relay/channel/aws/relay-aws.go | grep -A 20 "^func formatRequest"

# 同时查看 doAwsClientRequest 在 adaptor.go 中的调用
rg -n 'doAwsClientRequest' relay/channel/aws/adaptor.go -B 2 -A 2

Length of output: 142


🏁 Script executed:

#!/bin/bash
# 查看 relay-aws.go 中 formatRequest 附近的代码
awk '/^func formatRequest/,/^}/' relay/channel/aws/relay-aws.go | head -20

# 查看整个文件中 formatRequest 的定义
rg "^func formatRequest" relay/channel/aws/ -A 1

Length of output: 186


🏁 Script executed:

#!/bin/bash
# 查看 formatRequest 在 dto.go 中的完整实现
awk '/^func formatRequest/,/^}/' relay/channel/aws/dto.go

Length of output: 760


@seefs001 完美!现在我完全理解了。让我给你画一个清晰的流程图:

AWS 测试的实际流程

doAwsClientRequest 函数中(relay/channel/aws/relay-aws.go):

func doAwsClientRequest(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor, requestBody io.Reader) {
    // ...
    
    // 第 132 行:先读取 requestBody 参数
    awsClaudeReq, err := formatRequest(requestBody, requestHeader)  // ← 读取 requestBody
    
    // ...
    
    // 第 143/155 行:然后在透传模式下读取 c.Request.Body
    awsReq.Body, err = buildAwsRequestBody(c, info, awsClaudeReq)  // ← 读取 c.Request.Body
}

formatRequest 函数(dto.go):

func formatRequest(requestBody io.Reader, ...) {
    err := common.DecodeJson(requestBody, &awsClaudeRequest)  // ← 读取传入的 requestBody
}

buildAwsRequestBody 函数(relay-aws.go:166):

func buildAwsRequestBody(c *gin.Context, ...) {
    if 透传模式启用 {
        body, err := common.GetRequestBody(c)  // ← 读取 c.Request.Body
    }
}

问题所在

修改前的测试代码(两个变量指向同一个 buffer):

requestBody := bytes.NewBuffer(jsonData)
c.Request.Body = io.NopCloser(requestBody)  // ← 同一个实例!
resp, err := adaptor.DoRequest(c, info, requestBody)

执行流程:

1. formatRequest 读取 requestBody → buffer 被读完,指针到末尾
2. buildAwsRequestBody(透传模式)读取 c.Request.Body → 因为是同一个 buffer,已经空了!❌

修改后的测试代码(两个独立的 buffer):

requestBody := bytes.NewBuffer(jsonData)
c.Request.Body = io.NopCloser(bytes.NewBuffer(jsonData))  // ← 新的独立实例!
resp, err := adaptor.DoRequest(c, info, requestBody)

执行流程:

1. formatRequest 读取 requestBody(buffer A)→ buffer A 被读完 ✓
2. buildAwsRequestBody 读取 c.Request.Body(buffer B)→ buffer B 独立,数据完整 ✓

总结

你的问题问得非常好!AWS 测试确实同时使用了两者

  • 使用了 requestBody 参数:在 formatRequest 中解析请求
  • 使用了 c.Request.Body:在透传模式下通过 buildAwsRequestBodycommon.GetRequestBody(c) 读取

所以这个 PR 的修改是必需的,它保证了这两个读取操作互不干扰。这就是为什么"看起来啥也没改",但实际上解决了一个导致 AWS 透传测试失败的关键 bug!👍

@seefs001

Copy link
Copy Markdown
Collaborator

看了眼感觉这个是可以改的🤔

@seefs001 seefs001 reopened this Jan 25, 2026
@seefs001
seefs001 merged commit c49f820 into QuantumNous:main Jan 25, 2026
1 check passed
@daggeryu

Copy link
Copy Markdown
Contributor Author

这个提交主要修复的是Body重复读,第二次读会读到空的,必须buildAwsRequestBody方法的body, err := common.GetRequestBody(c)读到的是空值
func buildAwsRequestBody(c *gin.Context, info *relaycommon.RelayInfo, awsClaudeReq any) ([]byte, error) { if model_setting.GetGlobalSettings().PassThroughRequestEnabled || info.ChannelSetting.PassThroughBodyEnabled { body, err := common.GetRequestBody(c) if err != nil { return nil, errors.Wrap(err, "get request body for pass-through fail") } var data map[string]interface{} if err := common.Unmarshal(body, &data); err != nil { return nil, errors.Wrap(err, "pass-through unmarshal request body fail") } delete(data, "model") delete(data, "stream") return common.Marshal(data) } return common.Marshal(awsClaudeReq) }
但是开了透传后,AWS渠道还是不能测试,还需要加请求头anthropic_version才可以,但是别的一些渠道可能可以测试。

@seefs001

Copy link
Copy Markdown
Collaborator

这个提交主要修复的是Body重复读,第二次读会读到空的,必须buildAwsRequestBody方法的body, err := common.GetRequestBody(c)读到的是空值 func buildAwsRequestBody(c *gin.Context, info *relaycommon.RelayInfo, awsClaudeReq any) ([]byte, error) { if model_setting.GetGlobalSettings().PassThroughRequestEnabled || info.ChannelSetting.PassThroughBodyEnabled { body, err := common.GetRequestBody(c) if err != nil { return nil, errors.Wrap(err, "get request body for pass-through fail") } var data map[string]interface{} if err := common.Unmarshal(body, &data); err != nil { return nil, errors.Wrap(err, "pass-through unmarshal request body fail") } delete(data, "model") delete(data, "stream") return common.Marshal(data) } return common.Marshal(awsClaudeReq) } 但是开了透传后,AWS渠道还是不能测试,还需要加请求头anthropic_version才可以,但是别的一些渠道可能可以测试。

嗯感觉可以在靠后的地方加判定,没有这个version就设置一个

@feitianbubu

Copy link
Copy Markdown
Member
requestBody := bytes.NewBuffer(jsonData)
c.Request.Body = io.NopCloser(bytes.NewBuffer(jsonData))

这个修改让代码更难以阅读了
是否应该让业务统一走common.GetRequestBody(c) 更合适

ennnnny pushed a commit to ennnnny/new-api that referenced this pull request Mar 17, 2026
fix request pass-through aws channels can't test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants