feat: refine operations UI and transport limits

This commit is contained in:
hectorzhao
2026-08-13 10:42:59 +08:00
parent fb02cbcf39
commit 67fee21616
34 changed files with 314 additions and 188 deletions
+10 -1
View File
@@ -12,6 +12,8 @@ import (
"time"
)
const maxAPIResponseBodyBytes int64 = 4 * 1024 * 1024
func (s Server) post(ctx context.Context, path string, payload any, result any) error {
client := s.HTTPClient
if client == nil {
@@ -31,10 +33,17 @@ func (s Server) post(ctx context.Context, path string, payload any, result any)
return err
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(io.LimitReader(resp.Body, 64*1024))
// Read one byte beyond the supported boundary so an oversized upstream
// response is reported explicitly. Silently cutting JSON at the boundary
// turns a transport-capacity problem into a misleading syntax error and can
// leave recoverable downstream receipts stuck indefinitely.
responseBody, err := io.ReadAll(io.LimitReader(resp.Body, maxAPIResponseBodyBytes+1))
if err != nil {
return fmt.Errorf("read api response: %w", err)
}
if int64(len(responseBody)) > maxAPIResponseBodyBytes {
return fmt.Errorf("api response exceeds %d-byte limit", maxAPIResponseBodyBytes)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
detail := strings.TrimSpace(string(responseBody))
if detail == "" {
@@ -0,0 +1,46 @@
package inbound
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestPostAcceptsAPIResponseLargerThanLegacy64KiB(t *testing.T) {
payload := strings.Repeat("x", 128*1024)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]string{"payload": payload})
}))
defer server.Close()
var result struct {
Payload string `json:"payload"`
}
if err := (Server{APIBaseURL: server.URL}).post(context.Background(), "/large", map[string]string{"request": "ok"}, &result); err != nil {
t.Fatalf("post response larger than 64KiB: %v", err)
}
if result.Payload != payload {
t.Fatalf("payload length = %d, want %d", len(result.Payload), len(payload))
}
}
func TestPostRejectsAPIResponseBeyondFourMiBWithoutTruncatedJSONError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"payload":"` + strings.Repeat("x", int(maxAPIResponseBodyBytes)) + `"}`))
}))
defer server.Close()
var result map[string]any
err := (Server{APIBaseURL: server.URL}).post(context.Background(), "/too-large", map[string]string{"request": "ok"}, &result)
if err == nil || !strings.Contains(err.Error(), "api response exceeds 4194304-byte limit") {
t.Fatalf("error = %v, want explicit response-size error", err)
}
if strings.Contains(err.Error(), "unexpected end of JSON input") {
t.Fatalf("oversized response must not surface as truncated JSON: %v", err)
}
}