perf: further optimize http and body buffer handling

This commit is contained in:
yusing
2025-10-12 20:57:51 +08:00
parent eef994082c
commit c66de99fcb
9 changed files with 97 additions and 42 deletions

View File

@@ -11,6 +11,7 @@ import (
"github.com/yusing/godoxy/internal/metrics/period"
"github.com/yusing/godoxy/internal/metrics/systeminfo"
"github.com/yusing/goutils/http/httpheaders"
"github.com/yusing/goutils/synk"
)
type SystemInfoRequest struct {
@@ -68,8 +69,13 @@ func SystemInfo(c *gin.Context) {
maps.Copy(c.Writer.Header(), resp.Header)
c.Status(resp.StatusCode)
io.Copy(c.Writer, resp.Body)
buf := pool.Get()
defer pool.Put(buf)
io.CopyBuffer(c.Writer, resp.Body, buf)
} else {
agent.ReverseProxy(c.Writer, c.Request, agentPkg.EndpointSystemInfo)
}
}
var pool = synk.GetBytesPool()

View File

@@ -106,8 +106,9 @@ func (ep *Entrypoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (ep *Entrypoint) serveHTTP(w http.ResponseWriter, r *http.Request) {
if ep.accessLogger != nil {
w = accesslog.NewResponseRecorder(w)
defer ep.accessLogger.Log(r, w.(*accesslog.ResponseRecorder).Response())
rec := accesslog.NewResponseRecorder(w)
w = rec
defer ep.accessLogger.Log(r, rec.Response())
}
route := ep.findRouteFunc(r.Host)

View File

@@ -92,7 +92,7 @@ func (m *forwardAuthMiddleware) before(w http.ResponseWriter, r *http.Request) (
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
body, release, err := httputils.ReadAllBody(resp)
defer release()
defer release(body)
if err != nil {
ForwardAuth.LogError(r).Err(err).Msg("failed to read response body")

View File

@@ -32,6 +32,17 @@ func (m *modifyHTML) before(_ http.ResponseWriter, req *http.Request) bool {
return true
}
func readerWithRelease(b []byte, release func([]byte)) io.ReadCloser {
return ioutils.NewHookReadCloser(io.NopCloser(bytes.NewReader(b)), func() {
release(b)
})
}
type eofReader struct{}
func (eofReader) Read([]byte) (int, error) { return 0, io.EOF }
func (eofReader) Close() error { return nil }
// modifyResponse implements ResponseModifier.
func (m *modifyHTML) modifyResponse(resp *http.Response) error {
// including text/html and application/xhtml+xml
@@ -42,7 +53,9 @@ func (m *modifyHTML) modifyResponse(resp *http.Response) error {
// NOTE: do not put it in the defer, it will be used as resp.Body
content, release, err := httputils.ReadAllBody(resp)
if err != nil {
log.Err(err).Str("url", fullURL(resp.Request)).Msg("failed to read response body")
resp.Body.Close()
resp.Body = eofReader{}
return err
}
resp.Body.Close()
@@ -50,7 +63,7 @@ func (m *modifyHTML) modifyResponse(resp *http.Response) error {
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(content))
if err != nil {
// invalid html, restore the original body
resp.Body = io.NopCloser(bytes.NewReader(content))
resp.Body = readerWithRelease(content, release)
log.Err(err).Str("url", fullURL(resp.Request)).Msg("invalid html found")
return nil
}
@@ -58,7 +71,7 @@ func (m *modifyHTML) modifyResponse(resp *http.Response) error {
ele := doc.Find(m.Target)
if ele.Length() == 0 {
// no target found, restore the original body
resp.Body = io.NopCloser(bytes.NewReader(content))
resp.Body = readerWithRelease(content, release)
return nil
}
@@ -73,12 +86,18 @@ func (m *modifyHTML) modifyResponse(resp *http.Response) error {
buf := bytes.NewBuffer(content[:0])
err = buildHTML(doc, buf)
if err != nil {
log.Err(err).Str("url", fullURL(resp.Request)).Msg("failed to build html")
// invalid html, restore the original body
resp.Body = readerWithRelease(content, release)
return err
}
resp.ContentLength = int64(buf.Len())
resp.Header.Set("Content-Length", strconv.Itoa(buf.Len()))
resp.Header.Set("Content-Type", "text/html; charset=utf-8")
resp.Body = ioutils.NewHookReadCloser(io.NopCloser(bytes.NewReader(buf.Bytes())), release)
resp.Body = readerWithRelease(buf.Bytes(), func(_ []byte) {
// release content, not buf.Bytes()
release(content)
})
return nil
}

View File

@@ -1,7 +1,7 @@
package monitor
import (
"errors"
"fmt"
"net/http"
"net/url"
"time"
@@ -9,6 +9,7 @@ import (
"github.com/bytedance/sonic"
agentPkg "github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/internal/types"
httputils "github.com/yusing/goutils/http"
)
type (
@@ -62,17 +63,24 @@ func (mon *AgentProxiedMonitor) CheckHealth() (result types.HealthCheckResult, e
ctx, cancel := mon.ContextWithTimeout("timeout querying agent")
defer cancel()
data, status, err := mon.agent.DoHealthCheck(ctx, mon.endpointURL)
resp, err := mon.agent.DoHealthCheck(ctx, mon.endpointURL)
if err != nil {
return result, err
}
data, release, err := httputils.ReadAllBody(resp)
resp.Body.Close()
if err != nil {
return result, err
}
defer release(data)
endTime := time.Now()
switch status {
switch resp.StatusCode {
case http.StatusOK:
err = sonic.Unmarshal(data, &result)
default:
err = errors.New(string(data))
err = fmt.Errorf("HTTP %d %s", resp.StatusCode, data)
}
if err == nil && result.Latency != 0 {
// use godoxy to agent latency

View File

@@ -18,8 +18,15 @@ type HTTPHealthMonitor struct {
var pinger = &http.Client{
Transport: &http.Transport{
DisableKeepAlives: true,
ForceAttemptHTTP2: false,
DisableKeepAlives: true,
ForceAttemptHTTP2: false,
TLSHandshakeTimeout: 3 * time.Second,
ResponseHeaderTimeout: 5 * time.Second,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
MaxIdleConnsPerHost: 1,
IdleConnTimeout: 10 * time.Second,
},
CheckRedirect: func(r *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
@@ -51,13 +58,16 @@ func (mon *HTTPHealthMonitor) CheckHealth() (types.HealthCheckResult, error) {
return types.HealthCheckResult{}, err
}
req.Close = true
req.Header.Set("Connection", "close")
req.Header.Set("User-Agent", "GoDoxy/"+version.Get().String())
req.Header.Set("Accept", "text/plain,text/html,*/*;q=0.8")
req.Header.Set("Accept-Encoding", "identity")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Pragma", "no-cache")
start := time.Now()
resp, respErr := pinger.Do(req)
if respErr == nil {
defer resp.Body.Close()
resp.Body.Close()
}
lat := time.Since(start)