diff --git a/agent/pkg/handler/check_health_test.go b/agent/pkg/handler/check_health_test.go index 4633ed6..d90d98e 100644 --- a/agent/pkg/handler/check_health_test.go +++ b/agent/pkg/handler/check_health_test.go @@ -1,7 +1,6 @@ package handler_test import ( - "encoding/json" "net" "net/http" "net/http/httptest" @@ -9,6 +8,8 @@ import ( "strconv" "testing" + "github.com/yusing/go-proxy/pkg/json" + "github.com/stretchr/testify/require" "github.com/yusing/go-proxy/agent/pkg/agent" "github.com/yusing/go-proxy/agent/pkg/handler" diff --git a/internal/api/v1/auth/oidc.go b/internal/api/v1/auth/oidc.go index 2643a99..95ae628 100644 --- a/internal/api/v1/auth/oidc.go +++ b/internal/api/v1/auth/oidc.go @@ -4,7 +4,6 @@ import ( "context" "crypto/rand" "encoding/base64" - "encoding/json" "errors" "fmt" "io" @@ -15,6 +14,8 @@ import ( "strings" "time" + "github.com/yusing/go-proxy/pkg/json" + "github.com/coreos/go-oidc/v3/oidc" "github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/net/gphttp" diff --git a/internal/api/v1/auth/oidc_test.go b/internal/api/v1/auth/oidc_test.go index 0ed759f..ad2a5e2 100644 --- a/internal/api/v1/auth/oidc_test.go +++ b/internal/api/v1/auth/oidc_test.go @@ -5,12 +5,13 @@ import ( "crypto/rand" "crypto/rsa" "encoding/base64" - "encoding/json" "net/http" "net/http/httptest" "testing" "time" + "github.com/yusing/go-proxy/pkg/json" + "github.com/coreos/go-oidc/v3/oidc" "github.com/golang-jwt/jwt/v5" "github.com/yusing/go-proxy/internal/common" diff --git a/internal/api/v1/auth/userpass.go b/internal/api/v1/auth/userpass.go index 239a4cc..39dae3f 100644 --- a/internal/api/v1/auth/userpass.go +++ b/internal/api/v1/auth/userpass.go @@ -1,11 +1,12 @@ package auth import ( - "encoding/json" "fmt" "net/http" "time" + "github.com/yusing/go-proxy/pkg/json" + "github.com/golang-jwt/jwt/v5" "github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/gperr" diff --git a/internal/api/v1/auth/userpass_test.go b/internal/api/v1/auth/userpass_test.go index 9a9fbc4..cd958ed 100644 --- a/internal/api/v1/auth/userpass_test.go +++ b/internal/api/v1/auth/userpass_test.go @@ -2,13 +2,14 @@ package auth import ( "bytes" - "encoding/json" "io" "net/http" "net/http/httptest" "testing" "time" + "github.com/yusing/go-proxy/pkg/json" + . "github.com/yusing/go-proxy/internal/utils/testing" "golang.org/x/crypto/bcrypt" ) diff --git a/internal/api/v1/certapi/cert_info.go b/internal/api/v1/certapi/cert_info.go index 07edfd9..c99586c 100644 --- a/internal/api/v1/certapi/cert_info.go +++ b/internal/api/v1/certapi/cert_info.go @@ -1,9 +1,10 @@ package certapi import ( - "encoding/json" "net/http" + "github.com/yusing/go-proxy/pkg/json" + config "github.com/yusing/go-proxy/internal/config/types" ) diff --git a/internal/api/v1/dockerapi/info.go b/internal/api/v1/dockerapi/info.go index 373895d..6aeb4de 100644 --- a/internal/api/v1/dockerapi/info.go +++ b/internal/api/v1/dockerapi/info.go @@ -2,10 +2,11 @@ package dockerapi import ( "context" - "encoding/json" "net/http" "sort" + "github.com/yusing/go-proxy/pkg/json" + dockerSystem "github.com/docker/docker/api/types/system" "github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/utils/strutils" @@ -13,8 +14,8 @@ import ( type dockerInfo dockerSystem.Info -func (d *dockerInfo) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]any{ +func (d *dockerInfo) MarshalJSONTo(buf []byte) []byte { + return json.MarshalTo(map[string]any{ "name": d.Name, "version": d.ServerVersion, "containers": map[string]int{ @@ -25,8 +26,8 @@ func (d *dockerInfo) MarshalJSON() ([]byte, error) { }, "images": d.Images, "n_cpu": d.NCPU, - "memory": strutils.FormatByteSizeWithUnit(d.MemTotal), - }) + "memory": strutils.FormatByteSize(d.MemTotal), + }, buf) } func DockerInfo(w http.ResponseWriter, r *http.Request) { diff --git a/internal/api/v1/dockerapi/utils.go b/internal/api/v1/dockerapi/utils.go index b25b59a..51f23cf 100644 --- a/internal/api/v1/dockerapi/utils.go +++ b/internal/api/v1/dockerapi/utils.go @@ -2,10 +2,11 @@ package dockerapi import ( "context" - "encoding/json" "net/http" "time" + "github.com/yusing/go-proxy/pkg/json" + "github.com/coder/websocket" "github.com/coder/websocket/wsjson" "github.com/yusing/go-proxy/agent/pkg/agent" diff --git a/internal/api/v1/homepage_overrides.go b/internal/api/v1/homepage_overrides.go index 6c3d303..3cd46af 100644 --- a/internal/api/v1/homepage_overrides.go +++ b/internal/api/v1/homepage_overrides.go @@ -1,12 +1,12 @@ package v1 import ( - "encoding/json" "io" "net/http" "github.com/yusing/go-proxy/internal/homepage" "github.com/yusing/go-proxy/internal/net/gphttp" + "github.com/yusing/go-proxy/pkg/json" ) const ( diff --git a/internal/api/v1/new_agent.go b/internal/api/v1/new_agent.go index 9abc627..6b6daa5 100644 --- a/internal/api/v1/new_agent.go +++ b/internal/api/v1/new_agent.go @@ -1,13 +1,14 @@ package v1 import ( - "encoding/json" "fmt" "io" "net/http" "os" "strconv" + "github.com/yusing/go-proxy/pkg/json" + _ "embed" "github.com/yusing/go-proxy/agent/pkg/agent" diff --git a/internal/api/v1/query/query.go b/internal/api/v1/query/query.go index 4463834..df59adc 100644 --- a/internal/api/v1/query/query.go +++ b/internal/api/v1/query/query.go @@ -1,11 +1,12 @@ package query import ( - "encoding/json" "fmt" "io" "net/http" + "github.com/yusing/go-proxy/pkg/json" + v1 "github.com/yusing/go-proxy/internal/api/v1" "github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/gperr" diff --git a/internal/homepage/homepage.go b/internal/homepage/homepage.go index 9ad1d81..77dc620 100644 --- a/internal/homepage/homepage.go +++ b/internal/homepage/homepage.go @@ -1,9 +1,10 @@ package homepage import ( - "encoding/json" "strings" + "github.com/yusing/go-proxy/pkg/json" + config "github.com/yusing/go-proxy/internal/config/types" "github.com/yusing/go-proxy/internal/utils" ) @@ -42,7 +43,7 @@ func (cfg *ItemConfig) GetOverride(alias string) *ItemConfig { return overrideConfigInstance.GetOverride(alias, cfg) } -func (item *Item) MarshalJSON() ([]byte, error) { +func (item *Item) MarshalJSONTo(buf []byte) []byte { var url *string if !strings.ContainsRune(item.Alias, '.') { godoxyCfg := config.GetInstance().Value() @@ -55,7 +56,7 @@ func (item *Item) MarshalJSON() ([]byte, error) { } else { url = &item.Alias } - return json.Marshal(map[string]any{ + return json.MarshalTo(map[string]any{ "show": item.Show, "alias": item.Alias, "provider": item.Provider, @@ -66,7 +67,7 @@ func (item *Item) MarshalJSON() ([]byte, error) { "description": item.Description, "sort_order": item.SortOrder, "widget_config": item.WidgetConfig, - }) + }, buf) } func (c Homepage) Add(item *Item) { diff --git a/internal/homepage/icon_cache.go b/internal/homepage/icon_cache.go index b7f68ea..ac35db3 100644 --- a/internal/homepage/icon_cache.go +++ b/internal/homepage/icon_cache.go @@ -1,10 +1,11 @@ package homepage import ( - "encoding/json" "sync" "time" + "github.com/yusing/go-proxy/pkg/json" + "github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/task" diff --git a/internal/homepage/list-icons.go b/internal/homepage/list-icons.go index a76a87e..9979fe9 100644 --- a/internal/homepage/list-icons.go +++ b/internal/homepage/list-icons.go @@ -1,12 +1,13 @@ package homepage import ( - "encoding/json" "io" "net/http" "sync" "time" + "github.com/yusing/go-proxy/pkg/json" + "github.com/lithammer/fuzzysearch/fuzzy" "github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/logging" diff --git a/internal/metrics/period/entries.go b/internal/metrics/period/entries.go index d9a5554..db61431 100644 --- a/internal/metrics/period/entries.go +++ b/internal/metrics/period/entries.go @@ -1,8 +1,9 @@ package period import ( - "encoding/json" "time" + + "github.com/yusing/go-proxy/pkg/json" ) type Entries[T any] struct { @@ -48,11 +49,11 @@ func (e *Entries[T]) Get() []*T { return res } -func (e *Entries[T]) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]any{ +func (e *Entries[T]) MarshalJSONTo(buf []byte) []byte { + return json.MarshalTo(map[string]any{ "entries": e.Get(), "interval": e.interval, - }) + }, buf) } func (e *Entries[T]) UnmarshalJSON(data []byte) error { diff --git a/internal/metrics/period/poller.go b/internal/metrics/period/poller.go index f60014d..0e36e0d 100644 --- a/internal/metrics/period/poller.go +++ b/internal/metrics/period/poller.go @@ -2,7 +2,6 @@ package period import ( "context" - "encoding/json" "fmt" "net/url" "os" @@ -15,6 +14,7 @@ import ( "github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/task" "github.com/yusing/go-proxy/internal/utils/atomic" + "github.com/yusing/go-proxy/pkg/json" ) type ( diff --git a/internal/metrics/period/tests.go b/internal/metrics/period/tests.go new file mode 100644 index 0000000..94abac4 --- /dev/null +++ b/internal/metrics/period/tests.go @@ -0,0 +1,71 @@ +package period + +import ( + "context" + "net/http" + "net/url" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/yusing/go-proxy/pkg/json" +) + +func (p *Poller[T, AggregateT]) Test(t *testing.T, query url.Values) { + t.Helper() + for range 3 { + require.NoError(t, p.testPoll()) + } + t.Run("periods", func(t *testing.T) { + assert.NoError(t, p.testMarshalPeriods(query)) + }) + t.Run("no period", func(t *testing.T) { + assert.NoError(t, p.testMarshalNoPeriod()) + }) +} + +func (p *Poller[T, AggregateT]) testPeriod(period string, query url.Values) (any, error) { + query.Set("period", period) + return p.getRespData(&http.Request{URL: &url.URL{RawQuery: query.Encode()}}) +} + +func (p *Poller[T, AggregateT]) testPoll() error { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + data, err := p.poll(ctx, p.lastResult.Load()) + if err != nil { + return err + } + for _, period := range p.period.Entries { + period.Add(time.Now(), data) + } + p.lastResult.Store(data) + return nil +} + +func (p *Poller[T, AggregateT]) testMarshalPeriods(query url.Values) error { + for period := range p.period.Entries { + data, err := p.testPeriod(string(period), query) + if err != nil { + return err + } + _, err = json.Marshal(data) + if err != nil { + return err + } + } + return nil +} + +func (p *Poller[T, AggregateT]) testMarshalNoPeriod() error { + data, err := p.getRespData(&http.Request{URL: &url.URL{}}) + if err != nil { + return err + } + _, err = json.Marshal(data) + if err != nil { + return err + } + return nil +} diff --git a/internal/metrics/systeminfo/system_info.go b/internal/metrics/systeminfo/system_info.go index abe37b9..1d44c50 100644 --- a/internal/metrics/systeminfo/system_info.go +++ b/internal/metrics/systeminfo/system_info.go @@ -3,7 +3,6 @@ package systeminfo import ( "bytes" "context" - "encoding/json" "errors" "fmt" "net/url" @@ -20,6 +19,7 @@ import ( "github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/metrics/period" + "github.com/yusing/go-proxy/pkg/json" ) // json tags are left for tests @@ -55,7 +55,7 @@ type ( DownloadSpeed float64 `json:"download_speed"` } Sensors []sensors.TemperatureStat - Aggregated []map[string]any + Aggregated = json.MapSlice[any] ) type SystemInfo struct { @@ -295,8 +295,8 @@ func (s *SystemInfo) collectSensorsInfo(ctx context.Context) error { } // explicitly implement MarshalJSON to avoid reflection -func (s *SystemInfo) MarshalJSON() ([]byte, error) { - b := bytes.NewBuffer(make([]byte, 0, 1024)) +func (s *SystemInfo) MarshalJSONTo(buf []byte) []byte { + b := bytes.NewBuffer(buf) b.WriteRune('{') @@ -315,7 +315,7 @@ func (s *SystemInfo) MarshalJSON() ([]byte, error) { // memory b.WriteString(`,"memory":`) if s.Memory != nil { - b.WriteString(fmt.Sprintf( + b.Write(fmt.Appendf(nil, `{"total":%d,"available":%d,"used":%d,"used_percent":%s}`, s.Memory.Total, s.Memory.Available, @@ -329,13 +329,13 @@ func (s *SystemInfo) MarshalJSON() ([]byte, error) { // disk b.WriteString(`,"disks":`) if len(s.Disks) > 0 { - b.WriteString("{") + b.WriteRune('{') first := true for device, disk := range s.Disks { if !first { b.WriteRune(',') } - b.WriteString(fmt.Sprintf( + b.Write(fmt.Appendf(nil, `"%s":{"device":%q,"path":%q,"fstype":%q,"total":%d,"free":%d,"used":%d,"used_percent":%s}`, device, device, @@ -362,7 +362,7 @@ func (s *SystemInfo) MarshalJSON() ([]byte, error) { if !first { b.WriteRune(',') } - b.WriteString(fmt.Sprintf( + b.Write(fmt.Appendf(nil, `"%s":{"name":%q,"read_bytes":%d,"write_bytes":%d,"read_speed":%s,"write_speed":%s,"iops":%d}`, name, name, @@ -382,7 +382,7 @@ func (s *SystemInfo) MarshalJSON() ([]byte, error) { // network b.WriteString(`,"network":`) if s.Network != nil { - b.WriteString(fmt.Sprintf( + b.Write(fmt.Appendf(nil, `{"bytes_sent":%d,"bytes_recv":%d,"upload_speed":%s,"download_speed":%s}`, s.Network.BytesSent, s.Network.BytesRecv, @@ -396,13 +396,13 @@ func (s *SystemInfo) MarshalJSON() ([]byte, error) { // sensors b.WriteString(`,"sensors":`) if len(s.Sensors) > 0 { - b.WriteString("{") + b.WriteRune('{') first := true for _, sensor := range s.Sensors { if !first { b.WriteRune(',') } - b.WriteString(fmt.Sprintf( + b.Write(fmt.Appendf(nil, `%q:{"name":%q,"temperature":%s,"high":%s,"critical":%s}`, sensor.SensorKey, sensor.SensorKey, @@ -418,7 +418,7 @@ func (s *SystemInfo) MarshalJSON() ([]byte, error) { } b.WriteRune('}') - return []byte(b.String()), nil + return b.Bytes() } func (s *Sensors) UnmarshalJSON(data []byte) error { @@ -560,43 +560,3 @@ func aggregate(entries []*SystemInfo, query url.Values) (total int, result Aggre } return len(aggregated), aggregated } - -func (result Aggregated) MarshalJSON() ([]byte, error) { - buf := bytes.NewBuffer(make([]byte, 0, 1024)) - - buf.WriteByte('[') - i := 0 - n := len(result) - for _, entry := range result { - buf.WriteRune('{') - j := 0 - m := len(entry) - for k, v := range entry { - buf.WriteByte('"') - buf.WriteString(k) - buf.WriteByte('"') - buf.WriteByte(':') - switch v := v.(type) { - case float64: - buf.WriteString(strconv.FormatFloat(v, 'f', 2, 64)) - case uint64: - buf.WriteString(strconv.FormatUint(v, 10)) - case int64: - buf.WriteString(strconv.FormatInt(v, 10)) - default: - panic(fmt.Sprintf("unexpected type: %T", v)) - } - if j != m-1 { - buf.WriteByte(',') - } - j++ - } - buf.WriteByte('}') - if i != n-1 { - buf.WriteByte(',') - } - i++ - } - buf.WriteByte(']') - return buf.Bytes(), nil -} diff --git a/internal/metrics/systeminfo/system_info_test.go b/internal/metrics/systeminfo/system_info_test.go index 6579069..8c1906f 100644 --- a/internal/metrics/systeminfo/system_info_test.go +++ b/internal/metrics/systeminfo/system_info_test.go @@ -1,15 +1,23 @@ package systeminfo import ( - "encoding/json" "net/url" "reflect" "testing" "github.com/shirou/gopsutil/v4/sensors" . "github.com/yusing/go-proxy/internal/utils/testing" + "github.com/yusing/go-proxy/pkg/json" ) +func TestPoller(t *testing.T) { + for _, query := range allQueries { + t.Run(query, func(t *testing.T) { + Poller.Test(t, url.Values{"aggregate": []string{query}}) + }) + } +} + func TestExcludeDisks(t *testing.T) { tests := []struct { name string @@ -191,8 +199,7 @@ func TestSerialize(t *testing.T) { for _, query := range allQueries { t.Run(query, func(t *testing.T) { _, result := aggregate(entries, url.Values{"aggregate": []string{query}}) - s, err := result.MarshalJSON() - ExpectNoError(t, err) + s := result.MarshalJSONTo(nil) var v []map[string]any ExpectNoError(t, json.Unmarshal(s, &v)) ExpectEqual(t, len(v), len(result)) @@ -206,31 +213,3 @@ func TestSerialize(t *testing.T) { }) } } - -func BenchmarkJSONMarshal(b *testing.B) { - entries := make([]*SystemInfo, b.N) - for i := range b.N { - entries[i] = testInfo - } - queries := map[string]Aggregated{} - for _, query := range allQueries { - _, result := aggregate(entries, url.Values{"aggregate": []string{query}}) - queries[query] = result - } - b.ReportAllocs() - b.ResetTimer() - b.Run("optimized", func(b *testing.B) { - for b.Loop() { - for _, query := range allQueries { - _, _ = queries[query].MarshalJSON() - } - } - }) - b.Run("json", func(b *testing.B) { - for b.Loop() { - for _, query := range allQueries { - _, _ = json.Marshal([]map[string]any(queries[query])) - } - } - }) -} diff --git a/internal/metrics/uptime/status.go b/internal/metrics/uptime/status.go new file mode 100644 index 0000000..f101ae1 --- /dev/null +++ b/internal/metrics/uptime/status.go @@ -0,0 +1,22 @@ +package uptime + +import ( + "fmt" + + "github.com/yusing/go-proxy/internal/watcher/health" +) + +type Status struct { + Status health.Status + Latency int64 + Timestamp int64 +} + +type RouteStatuses map[string][]*Status + +func (s *Status) MarshalJSONTo(buf []byte) []byte { + return fmt.Appendf(buf, + `{"status":"%s","latency":"%d","timestamp":"%d"}`, + s.Status, s.Latency, s.Timestamp, + ) +} diff --git a/internal/metrics/uptime/uptime.go b/internal/metrics/uptime/uptime.go index 3162e3d..41b0732 100644 --- a/internal/metrics/uptime/uptime.go +++ b/internal/metrics/uptime/uptime.go @@ -2,7 +2,6 @@ package uptime import ( "context" - "encoding/json" "net/url" "sort" "time" @@ -13,20 +12,15 @@ import ( "github.com/yusing/go-proxy/internal/route/routes" "github.com/yusing/go-proxy/internal/route/routes/routequery" "github.com/yusing/go-proxy/internal/watcher/health" + "github.com/yusing/go-proxy/pkg/json" ) type ( StatusByAlias struct { - Map map[string]*routequery.HealthInfoRaw `json:"statuses"` - Timestamp int64 `json:"timestamp"` + Map json.Map[*routequery.HealthInfoRaw] `json:"statuses"` + Timestamp int64 `json:"timestamp"` } - Status struct { - Status health.Status `json:"status"` - Latency int64 `json:"latency"` - Timestamp int64 `json:"timestamp"` - } - RouteStatuses map[string][]*Status - Aggregated []map[string]any + Aggregated = json.MapSlice[any] ) var Poller = period.NewPoller("uptime", getStatuses, aggregateStatuses) @@ -124,7 +118,3 @@ func (rs RouteStatuses) aggregate(limit int, offset int) Aggregated { } return result } - -func (result Aggregated) MarshalJSON() ([]byte, error) { - return json.Marshal([]map[string]any(result)) -} diff --git a/internal/metrics/uptime/uptime_test.go b/internal/metrics/uptime/uptime_test.go new file mode 100644 index 0000000..8182aad --- /dev/null +++ b/internal/metrics/uptime/uptime_test.go @@ -0,0 +1,10 @@ +package uptime + +import ( + "net/url" + "testing" +) + +func TestPoller(t *testing.T) { + Poller.Test(t, url.Values{"limit": []string{"1"}}) +} diff --git a/internal/net/gphttp/accesslog/access_logger.go b/internal/net/gphttp/accesslog/access_logger.go index 1637377..3f56fa2 100644 --- a/internal/net/gphttp/accesslog/access_logger.go +++ b/internal/net/gphttp/accesslog/access_logger.go @@ -194,6 +194,6 @@ func (l *AccessLogger) write(data []byte) { if err != nil { l.handleErr(err) } else { - logging.Debug().Msg("access log flushed to " + l.io.Name()) + logging.Trace().Msg("access log flushed to " + l.io.Name()) } } diff --git a/internal/net/gphttp/accesslog/access_logger_test.go b/internal/net/gphttp/accesslog/access_logger_test.go index 7e7af45..ee903c4 100644 --- a/internal/net/gphttp/accesslog/access_logger_test.go +++ b/internal/net/gphttp/accesslog/access_logger_test.go @@ -2,13 +2,14 @@ package accesslog_test import ( "bytes" - "encoding/json" "fmt" "net/http" "net/url" "testing" "time" + "github.com/yusing/go-proxy/pkg/json" + . "github.com/yusing/go-proxy/internal/net/gphttp/accesslog" "github.com/yusing/go-proxy/internal/task" . "github.com/yusing/go-proxy/internal/utils/testing" diff --git a/internal/net/gphttp/accesslog/formatter.go b/internal/net/gphttp/accesslog/formatter.go index d0fcd68..deca013 100644 --- a/internal/net/gphttp/accesslog/formatter.go +++ b/internal/net/gphttp/accesslog/formatter.go @@ -2,13 +2,14 @@ package accesslog import ( "bytes" - "encoding/json" "net" "net/http" "net/url" "strconv" "time" + "github.com/yusing/go-proxy/pkg/json" + "github.com/yusing/go-proxy/internal/logging" ) diff --git a/internal/net/gphttp/body.go b/internal/net/gphttp/body.go index 3a94023..45d78e7 100644 --- a/internal/net/gphttp/body.go +++ b/internal/net/gphttp/body.go @@ -2,11 +2,12 @@ package gphttp import ( "context" - "encoding/json" "errors" "fmt" "net/http" + "github.com/yusing/go-proxy/pkg/json" + "github.com/yusing/go-proxy/internal/logging" ) diff --git a/internal/net/gphttp/error.go b/internal/net/gphttp/error.go index f269e3f..fc37077 100644 --- a/internal/net/gphttp/error.go +++ b/internal/net/gphttp/error.go @@ -2,11 +2,12 @@ package gphttp import ( "context" - "encoding/json" "errors" "net/http" "syscall" + "github.com/yusing/go-proxy/pkg/json" + "github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/net/gphttp/httpheaders" ) diff --git a/internal/net/gphttp/gpwebsocket/utils.go b/internal/net/gphttp/gpwebsocket/utils.go index cc98592..be4fa74 100644 --- a/internal/net/gphttp/gpwebsocket/utils.go +++ b/internal/net/gphttp/gpwebsocket/utils.go @@ -12,7 +12,6 @@ import ( "github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/net/gphttp" "github.com/yusing/go-proxy/internal/net/gphttp/httpheaders" - "github.com/yusing/go-proxy/internal/utils" ) func warnNoMatchDomains() { @@ -95,7 +94,7 @@ func WriteText(r *http.Request, conn *websocket.Conn, msg string) bool { func DynamicJSONHandler[ResultType any](w http.ResponseWriter, r *http.Request, getter func() ResultType, interval time.Duration) { if httpheaders.IsWebsocket(r.Header) { Periodic(w, r, interval, func(conn *websocket.Conn) error { - return wsjson.Write(r.Context(), conn, utils.ToJSONObject(getter())) + return wsjson.Write(r.Context(), conn, getter()) }) } else { gphttp.RespondJSON(w, r, getter()) diff --git a/internal/net/gphttp/middleware/middleware.go b/internal/net/gphttp/middleware/middleware.go index c6b5fd8..cc12aa9 100644 --- a/internal/net/gphttp/middleware/middleware.go +++ b/internal/net/gphttp/middleware/middleware.go @@ -1,12 +1,13 @@ package middleware import ( - "encoding/json" "net/http" "reflect" "sort" "strings" + "github.com/yusing/go-proxy/pkg/json" + "github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/logging" gphttp "github.com/yusing/go-proxy/internal/net/gphttp" @@ -158,12 +159,12 @@ func (m *Middleware) String() string { return m.name } -func (m *Middleware) MarshalJSON() ([]byte, error) { - return json.MarshalIndent(map[string]any{ +func (m *Middleware) MarshalJSONTo(buf []byte) []byte { + return json.MarshalTo(map[string]any{ "name": m.name, "options": m.impl, "priority": m.priority, - }, "", " ") + }, buf) } func (m *Middleware) ModifyRequest(next http.HandlerFunc, w http.ResponseWriter, r *http.Request) { diff --git a/internal/net/gphttp/middleware/middleware_builder_test.go b/internal/net/gphttp/middleware/middleware_builder_test.go index 08e8402..3036ca6 100644 --- a/internal/net/gphttp/middleware/middleware_builder_test.go +++ b/internal/net/gphttp/middleware/middleware_builder_test.go @@ -2,9 +2,10 @@ package middleware import ( _ "embed" - "encoding/json" "testing" + "github.com/yusing/go-proxy/pkg/json" + "github.com/yusing/go-proxy/internal/gperr" . "github.com/yusing/go-proxy/internal/utils/testing" ) @@ -16,7 +17,7 @@ func TestBuild(t *testing.T) { errs := gperr.NewBuilder("") middlewares := BuildMiddlewaresFromYAML("", testMiddlewareCompose, errs) ExpectNoError(t, errs.Error()) - Must(json.MarshalIndent(middlewares, "", " ")) + json.Marshal(middlewares) // t.Log(string(data)) // TODO: test } diff --git a/internal/net/gphttp/middleware/test_utils.go b/internal/net/gphttp/middleware/test_utils.go index edb5e24..a3eb34d 100644 --- a/internal/net/gphttp/middleware/test_utils.go +++ b/internal/net/gphttp/middleware/test_utils.go @@ -3,12 +3,13 @@ package middleware import ( "bytes" _ "embed" - "encoding/json" "io" "net/http" "net/http/httptest" "net/url" + "github.com/yusing/go-proxy/pkg/json" + "github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy" diff --git a/internal/notif/format.go b/internal/notif/format.go index f54d897..da51763 100644 --- a/internal/notif/format.go +++ b/internal/notif/format.go @@ -2,7 +2,8 @@ package notif import ( "bytes" - "encoding/json" + + "github.com/yusing/go-proxy/pkg/json" ) func formatMarkdown(extras LogFields) string { diff --git a/internal/notif/gotify.go b/internal/notif/gotify.go index bb2d951..390d4af 100644 --- a/internal/notif/gotify.go +++ b/internal/notif/gotify.go @@ -2,11 +2,12 @@ package notif import ( "bytes" - "encoding/json" "fmt" "io" "net/http" + "github.com/yusing/go-proxy/pkg/json" + "github.com/gotify/server/v2/model" "github.com/rs/zerolog" ) diff --git a/internal/notif/webhook.go b/internal/notif/webhook.go index 13fd23c..bccd5fc 100644 --- a/internal/notif/webhook.go +++ b/internal/notif/webhook.go @@ -2,12 +2,13 @@ package notif import ( _ "embed" - "encoding/json" "fmt" "io" "net/http" "strings" + "github.com/yusing/go-proxy/pkg/json" + "github.com/yusing/go-proxy/internal/gperr" ) @@ -101,10 +102,7 @@ func (webhook *Webhook) makeRespError(resp *http.Response) error { } func (webhook *Webhook) MakeBody(logMsg *LogMessage) (io.Reader, error) { - title, err := json.Marshal(logMsg.Title) - if err != nil { - return nil, err - } + title := json.String(logMsg.Title) fields, err := formatDiscord(logMsg.Extras) if err != nil { return nil, err @@ -115,13 +113,10 @@ func (webhook *Webhook) MakeBody(logMsg *LogMessage) (io.Reader, error) { } else { color = logMsg.Color.DecString() } - message, err := json.Marshal(formatMarkdown(logMsg.Extras)) - if err != nil { - return nil, err - } + message := json.String(formatMarkdown(logMsg.Extras)) plTempl := strings.NewReplacer( - "$title", string(title), - "$message", string(message), + "$title", title, + "$message", message, "$fields", fields, "$color", color, ) diff --git a/internal/route/routes/routequery/query.go b/internal/route/routes/routequery/query.go index f1b3037..d1bcf7f 100644 --- a/internal/route/routes/routequery/query.go +++ b/internal/route/routes/routequery/query.go @@ -26,8 +26,8 @@ func getHealthInfo(r route.Route) map[string]string { } type HealthInfoRaw struct { - Status health.Status - Latency time.Duration + Status health.Status `json:"status,string"` + Latency time.Duration `json:"latency"` } func getHealthInfoRaw(r route.Route) *HealthInfoRaw { diff --git a/internal/route/rules/do.go b/internal/route/rules/do.go index f113a4c..336a90b 100644 --- a/internal/route/rules/do.go +++ b/internal/route/rules/do.go @@ -300,7 +300,3 @@ func (cmd *Command) isBypass() bool { func (cmd *Command) String() string { return cmd.raw } - -func (cmd *Command) MarshalText() ([]byte, error) { - return []byte(cmd.String()), nil -} diff --git a/internal/route/rules/on.go b/internal/route/rules/on.go index f69e7a8..da3e453 100644 --- a/internal/route/rules/on.go +++ b/internal/route/rules/on.go @@ -261,10 +261,6 @@ func (on *RuleOn) String() string { return on.raw } -func (on *RuleOn) MarshalText() ([]byte, error) { - return []byte(on.String()), nil -} - func parseOn(line string) (Checker, gperr.Error) { ors := strutils.SplitRune(line, '|') diff --git a/internal/route/rules/rules.go b/internal/route/rules/rules.go index 4d1dbcc..032c523 100644 --- a/internal/route/rules/rules.go +++ b/internal/route/rules/rules.go @@ -1,8 +1,9 @@ package rules import ( - "encoding/json" "net/http" + + "github.com/yusing/go-proxy/pkg/json" ) type ( @@ -40,8 +41,8 @@ type ( */ Rule struct { Name string `json:"name"` - On RuleOn `json:"on"` - Do Command `json:"do"` + On RuleOn `json:"on,string"` + Do Command `json:"do,string"` } ) @@ -102,12 +103,12 @@ func (rules Rules) BuildHandler(caller string, up http.Handler) http.HandlerFunc } } -func (rules Rules) MarshalJSON() ([]byte, error) { +func (rules Rules) MarshalJSONTo(buf []byte) []byte { names := make([]string, len(rules)) for i, rule := range rules { names[i] = rule.Name } - return json.Marshal(names) + return json.MarshalTo(names, buf) } func (rule *Rule) String() string { diff --git a/internal/utils/atomic/value.go b/internal/utils/atomic/value.go index 65140fe..d05f80b 100644 --- a/internal/utils/atomic/value.go +++ b/internal/utils/atomic/value.go @@ -1,8 +1,9 @@ package atomic import ( - "encoding/json" "sync/atomic" + + "github.com/yusing/go-proxy/pkg/json" ) type Value[T any] struct { @@ -29,6 +30,6 @@ func (a *Value[T]) Swap(v T) T { return zero } -func (a *Value[T]) MarshalJSON() ([]byte, error) { - return json.Marshal(a.Load()) +func (a *Value[T]) MarshalJSONTo(buf []byte) []byte { + return json.MarshalTo(a.Load(), buf) } diff --git a/internal/utils/serialization.go b/internal/utils/serialization.go index ea0f0c8..c150a84 100644 --- a/internal/utils/serialization.go +++ b/internal/utils/serialization.go @@ -2,7 +2,6 @@ package utils import ( "bytes" - "encoding/json" "errors" "net" "net/url" @@ -12,6 +11,8 @@ import ( "strings" "time" + "github.com/yusing/go-proxy/pkg/json" + "github.com/go-playground/validator/v10" "github.com/goccy/go-yaml" "github.com/yusing/go-proxy/internal/gperr" @@ -22,12 +23,15 @@ import ( type SerializedObject = map[string]any type ( - MapMarshaller interface { + MapMarshaler interface { MarshalMap() map[string]any } - MapUnmarshaller interface { - UnmarshalMap(m map[string]any) gperr.Error -} + MapUnmarshaler interface { + UnmarshalMap(m map[string]any) gperr.Error + } + Marshaler interface { + MarshalJSONTo(buf *bytes.Buffer) error + } ) var ( @@ -51,11 +55,8 @@ var ( typeURL = reflect.TypeFor[url.URL]() typeCIDR = reflect.TypeFor[net.IPNet]() - typeMapMarshaller = reflect.TypeFor[MapMarshaller]() - typeMapUnmarshaler = reflect.TypeFor[MapUnmarshaller]() - typeJSONMarshaller = reflect.TypeFor[json.Marshaler]() - - typeAny = reflect.TypeOf((*any)(nil)).Elem() + typeMapUnmarshaler = reflect.TypeFor[MapUnmarshaler]() + typeStrParser = reflect.TypeFor[strutils.Parser]() ) var defaultValues = functional.NewMapOf[reflect.Type, func() any]() @@ -92,14 +93,14 @@ func extractFields(t reflect.Type) (all, anonymous []reflect.StructField) { if !field.IsExported() { continue } + // not checking tagJSON because json:"-" is for skipping json.Marshal if field.Tag.Get(tagDeserialize) == "-" { continue } if field.Anonymous { - f1, f2 := extractFields(field.Type) - fields = append(fields, f1...) + nested, _ := extractFields(field.Type) + fields = append(fields, nested...) anonymous = append(anonymous, field) - anonymous = append(anonymous, f2...) } else { fields = append(fields, field) } @@ -215,7 +216,7 @@ func MapUnmarshalValidate(src SerializedObject, dst any) (err gperr.Error) { if err != nil { return err } - return dstV.Addr().Interface().(MapUnmarshaller).UnmarshalMap(src) + return dstV.Addr().Interface().(MapUnmarshaler).UnmarshalMap(src) } dstV, dstT, err = dive(dstV) diff --git a/internal/watcher/health/monitor/agent_proxied.go b/internal/watcher/health/monitor/agent_proxied.go index 67db85c..b8aa102 100644 --- a/internal/watcher/health/monitor/agent_proxied.go +++ b/internal/watcher/health/monitor/agent_proxied.go @@ -1,11 +1,12 @@ package monitor import ( - "encoding/json" "errors" "net/http" "net/url" + "github.com/yusing/go-proxy/pkg/json" + agentPkg "github.com/yusing/go-proxy/agent/pkg/agent" "github.com/yusing/go-proxy/internal/watcher/health" ) diff --git a/internal/watcher/health/status.go b/internal/watcher/health/status.go index 355dd70..f58022c 100644 --- a/internal/watcher/health/status.go +++ b/internal/watcher/health/status.go @@ -1,6 +1,8 @@ package health -import "encoding/json" +import ( + "github.com/yusing/go-proxy/pkg/json" +) type Status uint8 @@ -35,10 +37,6 @@ func (s Status) String() string { } } -func (s Status) MarshalJSON() ([]byte, error) { - return []byte(`"` + s.String() + `"`), nil -} - func (s *Status) UnmarshalJSON(data []byte) error { var str string if err := json.Unmarshal(data, &str); err != nil { diff --git a/internal/watcher/health/types.go b/internal/watcher/health/types.go index de7ab11..8f83143 100644 --- a/internal/watcher/health/types.go +++ b/internal/watcher/health/types.go @@ -24,7 +24,7 @@ type ( task.TaskStarter task.TaskFinisher fmt.Stringer - utils.MapMarshaller + utils.MapMarshaler WithHealthInfo Name() string }