refactor(relay): move replay metadata onto request bodies

This commit is contained in:
CaIon
2026-08-06 17:33:10 +08:00
parent d6b5ce99de
commit ea4f021012
20 changed files with 211 additions and 267 deletions
+29 -4
View File
@@ -29,6 +29,14 @@ type BodyStorage interface {
NewReader() (io.ReadCloser, error)
}
// ReplayableBody is an outbound request body that can report its byte size and
// create independent readers for transport-level retries.
type ReplayableBody interface {
io.Reader
Size() int64
NewReader() (io.ReadCloser, error)
}
// ErrStorageClosed 存储已关闭错误
var ErrStorageClosed = fmt.Errorf("body storage is closed")
@@ -339,10 +347,27 @@ func CreateBodyStorageFromReader(reader io.Reader, contentLength int64, maxBytes
return storage, nil
}
// ReaderOnly wraps an io.Reader to hide io.Closer, preventing http.NewRequest
// from type-asserting io.ReadCloser and closing the underlying BodyStorage.
func ReaderOnly(r io.Reader) io.Reader {
return struct{ io.Reader }{r}
type replayableBodyReader struct {
storage BodyStorage
}
func (r replayableBodyReader) Read(p []byte) (int, error) {
return r.storage.Read(p)
}
func (r replayableBodyReader) Size() int64 {
return r.storage.Size()
}
func (r replayableBodyReader) NewReader() (io.ReadCloser, error) {
return r.storage.NewReader()
}
// NewReplayableBodyReader exposes the replay capabilities of storage without
// exposing io.Closer. This keeps ownership of the storage lifecycle with the
// caller instead of allowing net/http to close it as the request body.
func NewReplayableBodyReader(storage BodyStorage) ReplayableBody {
return replayableBodyReader{storage: storage}
}
// CleanupOldCacheFiles 清理旧的缓存文件(用于启动时清理残留)
+37
View File
@@ -0,0 +1,37 @@
package common
import (
"io"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewReplayableBodyReaderKeepsStorageLifecycleWithCaller(t *testing.T) {
payload := []byte(`{"model":"test-model","input":"hello"}`)
storage, err := CreateBodyStorage(payload)
require.NoError(t, err)
defer storage.Close()
body := NewReplayableBodyReader(storage)
assert.EqualValues(t, len(payload), body.Size())
_, exposesCloser := any(body).(io.Closer)
assert.False(t, exposesCloser, "the request body must not expose the storage closer")
req, err := http.NewRequest(http.MethodPost, "https://example.com", body)
require.NoError(t, err)
require.NoError(t, req.Body.Close())
replayBody, err := body.NewReader()
require.NoError(t, err, "closing the HTTP request body must not close the storage")
replay, err := io.ReadAll(replayBody)
require.NoError(t, err)
require.NoError(t, replayBody.Close())
assert.Equal(t, payload, replay)
require.NoError(t, storage.Close())
_, err = body.NewReader()
require.ErrorIs(t, err, ErrStorageClosed)
}