mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-05-06 06:00:44 +08:00
Codex CLI 0.125+ defaults to sending request bodies with Content-Encoding: zstd. Without server-side decompression the gateway returns 'Failed to parse request body' on /v1/responses (and any other JSON endpoint) because gjson sees raw zstd bytes. ReadRequestBodyWithPrealloc now inspects Content-Encoding and transparently decodes zstd, gzip/x-gzip, and deflate bodies before returning them, then strips the encoding headers and updates ContentLength so downstream code can reuse the bytes safely. Unsupported encodings produce a clear error. Adds unit tests covering identity, zstd, gzip, deflate, unsupported encoding, corrupt zstd payloads, nil bodies, and explicit identity.
91 lines
2.0 KiB
Go
91 lines
2.0 KiB
Go
package httputil
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"compress/zlib"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/klauspost/compress/zstd"
|
|
)
|
|
|
|
const (
|
|
requestBodyReadInitCap = 512
|
|
requestBodyReadMaxInitCap = 1 << 20
|
|
)
|
|
|
|
// ReadRequestBodyWithPrealloc reads request body with preallocated buffer based
|
|
// on content length, transparently decoding any Content-Encoding the upstream
|
|
// client used to compress the body (zstd, gzip, deflate).
|
|
func ReadRequestBodyWithPrealloc(req *http.Request) ([]byte, error) {
|
|
if req == nil || req.Body == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
capHint := requestBodyReadInitCap
|
|
if req.ContentLength > 0 {
|
|
switch {
|
|
case req.ContentLength < int64(requestBodyReadInitCap):
|
|
capHint = requestBodyReadInitCap
|
|
case req.ContentLength > int64(requestBodyReadMaxInitCap):
|
|
capHint = requestBodyReadMaxInitCap
|
|
default:
|
|
capHint = int(req.ContentLength)
|
|
}
|
|
}
|
|
|
|
buf := bytes.NewBuffer(make([]byte, 0, capHint))
|
|
if _, err := io.Copy(buf, req.Body); err != nil {
|
|
return nil, err
|
|
}
|
|
raw := buf.Bytes()
|
|
|
|
enc := strings.ToLower(strings.TrimSpace(req.Header.Get("Content-Encoding")))
|
|
if enc == "" || enc == "identity" {
|
|
return raw, nil
|
|
}
|
|
|
|
decoded, err := decompressRequestBody(enc, raw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode Content-Encoding %q: %w", enc, err)
|
|
}
|
|
|
|
req.Header.Del("Content-Encoding")
|
|
req.Header.Del("Content-Length")
|
|
req.ContentLength = int64(len(decoded))
|
|
|
|
return decoded, nil
|
|
}
|
|
|
|
func decompressRequestBody(encoding string, raw []byte) ([]byte, error) {
|
|
switch encoding {
|
|
case "zstd":
|
|
dec, err := zstd.NewReader(bytes.NewReader(raw))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer dec.Close()
|
|
return io.ReadAll(dec)
|
|
case "gzip", "x-gzip":
|
|
gr, err := gzip.NewReader(bytes.NewReader(raw))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer gr.Close()
|
|
return io.ReadAll(gr)
|
|
case "deflate":
|
|
zr, err := zlib.NewReader(bytes.NewReader(raw))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer zr.Close()
|
|
return io.ReadAll(zr)
|
|
default:
|
|
return nil, errors.New("unsupported Content-Encoding")
|
|
}
|
|
}
|