fix(cli): carry the review fixes of the master port to v1.0

This commit is contained in:
Anthony Wong authored and Cuong Manh Le committed 2026-09-28 16:51:55 +07:00
1 parent 12ab2a0e71
commit f52dfd0354
24 files changed
+993 -68

No files matched your search

+47 -1
View File
@@ -10,6 +10,7 @@ import (
"io"
"net"
"net/http"
"os"
"runtime"
"strings"
"time"
@@ -289,10 +290,18 @@ func SendLogs(ctx context.Context, lr *LogsRequest, cdDev bool) error {
if cdDev {
apiUrl = logURLDev
}
req, err := http.NewRequestWithContext(ctx, "POST", apiUrl, lr.Data)
body, size, getBody, cleanup, err := spoolLogBody(lr.Data)
if err != nil {
return err
}
defer cleanup()
req, err := http.NewRequestWithContext(ctx, "POST", apiUrl, body)
if err != nil {
return fmt.Errorf("http.NewRequest: %w", err)
}
req.ContentLength = size
req.GetBody = getBody
q := req.URL.Query()
q.Set("uid", lr.UID)
req.URL.RawQuery = q.Encode()
@@ -418,6 +427,15 @@ func doWithFallback(client *http.Client, req *http.Request, apiIp string) (*http
ipReq := req.Clone(req.Context())
ipReq.Host = apiIp
ipReq.URL.Host = apiIp
// The first attempt consumed the body. A request that can give its body
// again sends the whole body a second time.
if req.GetBody != nil {
body, bodyErr := req.GetBody()
if bodyErr != nil {
return nil, fmt.Errorf("request failed: %w; fallback to direct ip %s failed: %w", err, apiIp, bodyErr)
}
ipReq.Body = body
}
resp, fallbackErr := client.Do(ipReq)
if fallbackErr != nil {
return nil, fmt.Errorf("request failed: %w; fallback to direct ip %s failed: %w", err, apiIp, fallbackErr)
@@ -425,6 +443,34 @@ func doWithFallback(client *http.Client, req *http.Request, apiIp string) (*http
return resp, nil
}
// spoolLogBody copies an upload to a temporary file, so the request can go
// out a second time to the direct IP of the API. A log reader yields its
// bytes once, and the first attempt consumes them. The caller runs cleanup
// after the response arrived.
func spoolLogBody(data io.Reader) (body *os.File, size int64, getBody func() (io.ReadCloser, error), cleanup func(), err error) {
f, err := os.CreateTemp("", "ctrld-log-upload-*")
if err != nil {
return nil, 0, nil, nil, fmt.Errorf("creating the upload spool: %w", err)
}
cleanup = func() {
_ = f.Close()
_ = os.Remove(f.Name())
}
size, err = io.Copy(f, data)
if err != nil {
cleanup()
return nil, 0, nil, nil, fmt.Errorf("writing the upload spool: %w", err)
}
if _, err := f.Seek(0, io.SeekStart); err != nil {
cleanup()
return nil, 0, nil, nil, fmt.Errorf("rewinding the upload spool: %w", err)
}
// The transport closes the body of a failed attempt, so the second body
// is a new handle on the same file.
getBody = func() (io.ReadCloser, error) { return os.Open(f.Name()) }
return f, size, getBody, cleanup, nil
}
// apiServerIP returns the direct IP to connect to API server.
func apiServerIP(cdDev bool) string {
if cdDev {
+49
View File
@@ -0,0 +1,49 @@
package controld
import (
"context"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
// TestDoWithFallbackResendsTheSpooledBody drives the two-attempt upload: the
// first attempt fails on a closed port and the fallback to the direct IP must
// carry the whole body, which the first attempt consumed.
func TestDoWithFallbackResendsTheSpooledBody(t *testing.T) {
payload := strings.Repeat("log line\n", 4096)
var got string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, _ := io.ReadAll(r.Body)
got = string(b)
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
body, size, getBody, cleanup, err := spoolLogBody(strings.NewReader(payload))
if err != nil {
t.Fatalf("spoolLogBody: %v", err)
}
defer cleanup()
if size != int64(len(payload)) {
t.Fatalf("size = %d, want %d", size, len(payload))
}
// Port 1 refuses the connection, so the first attempt fails at once.
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "http://127.0.0.1:1/logs", body)
if err != nil {
t.Fatalf("NewRequest: %v", err)
}
req.ContentLength = size
req.GetBody = getBody
resp, err := doWithFallback(srv.Client(), req, strings.TrimPrefix(srv.URL, "http://"))
if err != nil {
t.Fatalf("doWithFallback: %v", err)
}
resp.Body.Close()
if got != payload {
t.Fatalf("the fallback carried %d bytes, want %d", len(got), len(payload))
}
}