refactor: refactor to adapt new custom json marshaler

This commit is contained in:
yusing 2025-04-16 14:39:26 +08:00
parent cdfc9d553b
commit c2b606e63e
43 changed files with 232 additions and 189 deletions

View file

@ -1,7 +1,6 @@
package handler_test package handler_test
import ( import (
"encoding/json"
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -9,6 +8,8 @@ import (
"strconv" "strconv"
"testing" "testing"
"github.com/yusing/go-proxy/pkg/json"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/yusing/go-proxy/agent/pkg/agent" "github.com/yusing/go-proxy/agent/pkg/agent"
"github.com/yusing/go-proxy/agent/pkg/handler" "github.com/yusing/go-proxy/agent/pkg/handler"

View file

@ -4,7 +4,6 @@ import (
"context" "context"
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -15,6 +14,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/yusing/go-proxy/pkg/json"
"github.com/coreos/go-oidc/v3/oidc" "github.com/coreos/go-oidc/v3/oidc"
"github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/net/gphttp" "github.com/yusing/go-proxy/internal/net/gphttp"

View file

@ -5,12 +5,13 @@ import (
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"encoding/base64" "encoding/base64"
"encoding/json"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"time" "time"
"github.com/yusing/go-proxy/pkg/json"
"github.com/coreos/go-oidc/v3/oidc" "github.com/coreos/go-oidc/v3/oidc"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/common"

View file

@ -1,11 +1,12 @@
package auth package auth
import ( import (
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"time" "time"
"github.com/yusing/go-proxy/pkg/json"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"

View file

@ -2,13 +2,14 @@ package auth
import ( import (
"bytes" "bytes"
"encoding/json"
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"time" "time"
"github.com/yusing/go-proxy/pkg/json"
. "github.com/yusing/go-proxy/internal/utils/testing" . "github.com/yusing/go-proxy/internal/utils/testing"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )

View file

@ -1,9 +1,10 @@
package certapi package certapi
import ( import (
"encoding/json"
"net/http" "net/http"
"github.com/yusing/go-proxy/pkg/json"
config "github.com/yusing/go-proxy/internal/config/types" config "github.com/yusing/go-proxy/internal/config/types"
) )

View file

@ -2,10 +2,11 @@ package dockerapi
import ( import (
"context" "context"
"encoding/json"
"net/http" "net/http"
"sort" "sort"
"github.com/yusing/go-proxy/pkg/json"
dockerSystem "github.com/docker/docker/api/types/system" dockerSystem "github.com/docker/docker/api/types/system"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/utils/strutils" "github.com/yusing/go-proxy/internal/utils/strutils"
@ -13,8 +14,8 @@ import (
type dockerInfo dockerSystem.Info type dockerInfo dockerSystem.Info
func (d *dockerInfo) MarshalJSON() ([]byte, error) { func (d *dockerInfo) MarshalJSONTo(buf []byte) []byte {
return json.Marshal(map[string]any{ return json.MarshalTo(map[string]any{
"name": d.Name, "name": d.Name,
"version": d.ServerVersion, "version": d.ServerVersion,
"containers": map[string]int{ "containers": map[string]int{
@ -25,8 +26,8 @@ func (d *dockerInfo) MarshalJSON() ([]byte, error) {
}, },
"images": d.Images, "images": d.Images,
"n_cpu": d.NCPU, "n_cpu": d.NCPU,
"memory": strutils.FormatByteSizeWithUnit(d.MemTotal), "memory": strutils.FormatByteSize(d.MemTotal),
}) }, buf)
} }
func DockerInfo(w http.ResponseWriter, r *http.Request) { func DockerInfo(w http.ResponseWriter, r *http.Request) {

View file

@ -2,10 +2,11 @@ package dockerapi
import ( import (
"context" "context"
"encoding/json"
"net/http" "net/http"
"time" "time"
"github.com/yusing/go-proxy/pkg/json"
"github.com/coder/websocket" "github.com/coder/websocket"
"github.com/coder/websocket/wsjson" "github.com/coder/websocket/wsjson"
"github.com/yusing/go-proxy/agent/pkg/agent" "github.com/yusing/go-proxy/agent/pkg/agent"

View file

@ -1,12 +1,12 @@
package v1 package v1
import ( import (
"encoding/json"
"io" "io"
"net/http" "net/http"
"github.com/yusing/go-proxy/internal/homepage" "github.com/yusing/go-proxy/internal/homepage"
"github.com/yusing/go-proxy/internal/net/gphttp" "github.com/yusing/go-proxy/internal/net/gphttp"
"github.com/yusing/go-proxy/pkg/json"
) )
const ( const (

View file

@ -1,13 +1,14 @@
package v1 package v1
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
"github.com/yusing/go-proxy/pkg/json"
_ "embed" _ "embed"
"github.com/yusing/go-proxy/agent/pkg/agent" "github.com/yusing/go-proxy/agent/pkg/agent"

View file

@ -1,11 +1,12 @@
package query package query
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"github.com/yusing/go-proxy/pkg/json"
v1 "github.com/yusing/go-proxy/internal/api/v1" v1 "github.com/yusing/go-proxy/internal/api/v1"
"github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"

View file

@ -1,9 +1,10 @@
package homepage package homepage
import ( import (
"encoding/json"
"strings" "strings"
"github.com/yusing/go-proxy/pkg/json"
config "github.com/yusing/go-proxy/internal/config/types" config "github.com/yusing/go-proxy/internal/config/types"
"github.com/yusing/go-proxy/internal/utils" "github.com/yusing/go-proxy/internal/utils"
) )
@ -42,7 +43,7 @@ func (cfg *ItemConfig) GetOverride(alias string) *ItemConfig {
return overrideConfigInstance.GetOverride(alias, cfg) return overrideConfigInstance.GetOverride(alias, cfg)
} }
func (item *Item) MarshalJSON() ([]byte, error) { func (item *Item) MarshalJSONTo(buf []byte) []byte {
var url *string var url *string
if !strings.ContainsRune(item.Alias, '.') { if !strings.ContainsRune(item.Alias, '.') {
godoxyCfg := config.GetInstance().Value() godoxyCfg := config.GetInstance().Value()
@ -55,7 +56,7 @@ func (item *Item) MarshalJSON() ([]byte, error) {
} else { } else {
url = &item.Alias url = &item.Alias
} }
return json.Marshal(map[string]any{ return json.MarshalTo(map[string]any{
"show": item.Show, "show": item.Show,
"alias": item.Alias, "alias": item.Alias,
"provider": item.Provider, "provider": item.Provider,
@ -66,7 +67,7 @@ func (item *Item) MarshalJSON() ([]byte, error) {
"description": item.Description, "description": item.Description,
"sort_order": item.SortOrder, "sort_order": item.SortOrder,
"widget_config": item.WidgetConfig, "widget_config": item.WidgetConfig,
}) }, buf)
} }
func (c Homepage) Add(item *Item) { func (c Homepage) Add(item *Item) {

View file

@ -1,10 +1,11 @@
package homepage package homepage
import ( import (
"encoding/json"
"sync" "sync"
"time" "time"
"github.com/yusing/go-proxy/pkg/json"
"github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/task" "github.com/yusing/go-proxy/internal/task"

View file

@ -1,12 +1,13 @@
package homepage package homepage
import ( import (
"encoding/json"
"io" "io"
"net/http" "net/http"
"sync" "sync"
"time" "time"
"github.com/yusing/go-proxy/pkg/json"
"github.com/lithammer/fuzzysearch/fuzzy" "github.com/lithammer/fuzzysearch/fuzzy"
"github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/logging"

View file

@ -1,8 +1,9 @@
package period package period
import ( import (
"encoding/json"
"time" "time"
"github.com/yusing/go-proxy/pkg/json"
) )
type Entries[T any] struct { type Entries[T any] struct {
@ -48,11 +49,11 @@ func (e *Entries[T]) Get() []*T {
return res return res
} }
func (e *Entries[T]) MarshalJSON() ([]byte, error) { func (e *Entries[T]) MarshalJSONTo(buf []byte) []byte {
return json.Marshal(map[string]any{ return json.MarshalTo(map[string]any{
"entries": e.Get(), "entries": e.Get(),
"interval": e.interval, "interval": e.interval,
}) }, buf)
} }
func (e *Entries[T]) UnmarshalJSON(data []byte) error { func (e *Entries[T]) UnmarshalJSON(data []byte) error {

View file

@ -2,7 +2,6 @@ package period
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
@ -15,6 +14,7 @@ import (
"github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/task" "github.com/yusing/go-proxy/internal/task"
"github.com/yusing/go-proxy/internal/utils/atomic" "github.com/yusing/go-proxy/internal/utils/atomic"
"github.com/yusing/go-proxy/pkg/json"
) )
type ( type (

View file

@ -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
}

View file

@ -3,7 +3,6 @@ package systeminfo
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
@ -20,6 +19,7 @@ import (
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/metrics/period" "github.com/yusing/go-proxy/internal/metrics/period"
"github.com/yusing/go-proxy/pkg/json"
) )
// json tags are left for tests // json tags are left for tests
@ -55,7 +55,7 @@ type (
DownloadSpeed float64 `json:"download_speed"` DownloadSpeed float64 `json:"download_speed"`
} }
Sensors []sensors.TemperatureStat Sensors []sensors.TemperatureStat
Aggregated []map[string]any Aggregated = json.MapSlice[any]
) )
type SystemInfo struct { type SystemInfo struct {
@ -295,8 +295,8 @@ func (s *SystemInfo) collectSensorsInfo(ctx context.Context) error {
} }
// explicitly implement MarshalJSON to avoid reflection // explicitly implement MarshalJSON to avoid reflection
func (s *SystemInfo) MarshalJSON() ([]byte, error) { func (s *SystemInfo) MarshalJSONTo(buf []byte) []byte {
b := bytes.NewBuffer(make([]byte, 0, 1024)) b := bytes.NewBuffer(buf)
b.WriteRune('{') b.WriteRune('{')
@ -315,7 +315,7 @@ func (s *SystemInfo) MarshalJSON() ([]byte, error) {
// memory // memory
b.WriteString(`,"memory":`) b.WriteString(`,"memory":`)
if s.Memory != nil { if s.Memory != nil {
b.WriteString(fmt.Sprintf( b.Write(fmt.Appendf(nil,
`{"total":%d,"available":%d,"used":%d,"used_percent":%s}`, `{"total":%d,"available":%d,"used":%d,"used_percent":%s}`,
s.Memory.Total, s.Memory.Total,
s.Memory.Available, s.Memory.Available,
@ -329,13 +329,13 @@ func (s *SystemInfo) MarshalJSON() ([]byte, error) {
// disk // disk
b.WriteString(`,"disks":`) b.WriteString(`,"disks":`)
if len(s.Disks) > 0 { if len(s.Disks) > 0 {
b.WriteString("{") b.WriteRune('{')
first := true first := true
for device, disk := range s.Disks { for device, disk := range s.Disks {
if !first { if !first {
b.WriteRune(',') 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}`, `"%s":{"device":%q,"path":%q,"fstype":%q,"total":%d,"free":%d,"used":%d,"used_percent":%s}`,
device, device,
device, device,
@ -362,7 +362,7 @@ func (s *SystemInfo) MarshalJSON() ([]byte, error) {
if !first { if !first {
b.WriteRune(',') 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}`, `"%s":{"name":%q,"read_bytes":%d,"write_bytes":%d,"read_speed":%s,"write_speed":%s,"iops":%d}`,
name, name,
name, name,
@ -382,7 +382,7 @@ func (s *SystemInfo) MarshalJSON() ([]byte, error) {
// network // network
b.WriteString(`,"network":`) b.WriteString(`,"network":`)
if s.Network != nil { 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}`, `{"bytes_sent":%d,"bytes_recv":%d,"upload_speed":%s,"download_speed":%s}`,
s.Network.BytesSent, s.Network.BytesSent,
s.Network.BytesRecv, s.Network.BytesRecv,
@ -396,13 +396,13 @@ func (s *SystemInfo) MarshalJSON() ([]byte, error) {
// sensors // sensors
b.WriteString(`,"sensors":`) b.WriteString(`,"sensors":`)
if len(s.Sensors) > 0 { if len(s.Sensors) > 0 {
b.WriteString("{") b.WriteRune('{')
first := true first := true
for _, sensor := range s.Sensors { for _, sensor := range s.Sensors {
if !first { if !first {
b.WriteRune(',') b.WriteRune(',')
} }
b.WriteString(fmt.Sprintf( b.Write(fmt.Appendf(nil,
`%q:{"name":%q,"temperature":%s,"high":%s,"critical":%s}`, `%q:{"name":%q,"temperature":%s,"high":%s,"critical":%s}`,
sensor.SensorKey, sensor.SensorKey,
sensor.SensorKey, sensor.SensorKey,
@ -418,7 +418,7 @@ func (s *SystemInfo) MarshalJSON() ([]byte, error) {
} }
b.WriteRune('}') b.WriteRune('}')
return []byte(b.String()), nil return b.Bytes()
} }
func (s *Sensors) UnmarshalJSON(data []byte) error { 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 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
}

View file

@ -1,15 +1,23 @@
package systeminfo package systeminfo
import ( import (
"encoding/json"
"net/url" "net/url"
"reflect" "reflect"
"testing" "testing"
"github.com/shirou/gopsutil/v4/sensors" "github.com/shirou/gopsutil/v4/sensors"
. "github.com/yusing/go-proxy/internal/utils/testing" . "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) { func TestExcludeDisks(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -191,8 +199,7 @@ func TestSerialize(t *testing.T) {
for _, query := range allQueries { for _, query := range allQueries {
t.Run(query, func(t *testing.T) { t.Run(query, func(t *testing.T) {
_, result := aggregate(entries, url.Values{"aggregate": []string{query}}) _, result := aggregate(entries, url.Values{"aggregate": []string{query}})
s, err := result.MarshalJSON() s := result.MarshalJSONTo(nil)
ExpectNoError(t, err)
var v []map[string]any var v []map[string]any
ExpectNoError(t, json.Unmarshal(s, &v)) ExpectNoError(t, json.Unmarshal(s, &v))
ExpectEqual(t, len(v), len(result)) 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]))
}
}
})
}

View file

@ -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,
)
}

View file

@ -2,7 +2,6 @@ package uptime
import ( import (
"context" "context"
"encoding/json"
"net/url" "net/url"
"sort" "sort"
"time" "time"
@ -13,20 +12,15 @@ import (
"github.com/yusing/go-proxy/internal/route/routes" "github.com/yusing/go-proxy/internal/route/routes"
"github.com/yusing/go-proxy/internal/route/routes/routequery" "github.com/yusing/go-proxy/internal/route/routes/routequery"
"github.com/yusing/go-proxy/internal/watcher/health" "github.com/yusing/go-proxy/internal/watcher/health"
"github.com/yusing/go-proxy/pkg/json"
) )
type ( type (
StatusByAlias struct { StatusByAlias struct {
Map map[string]*routequery.HealthInfoRaw `json:"statuses"` Map json.Map[*routequery.HealthInfoRaw] `json:"statuses"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
} }
Status struct { Aggregated = json.MapSlice[any]
Status health.Status `json:"status"`
Latency int64 `json:"latency"`
Timestamp int64 `json:"timestamp"`
}
RouteStatuses map[string][]*Status
Aggregated []map[string]any
) )
var Poller = period.NewPoller("uptime", getStatuses, aggregateStatuses) var Poller = period.NewPoller("uptime", getStatuses, aggregateStatuses)
@ -124,7 +118,3 @@ func (rs RouteStatuses) aggregate(limit int, offset int) Aggregated {
} }
return result return result
} }
func (result Aggregated) MarshalJSON() ([]byte, error) {
return json.Marshal([]map[string]any(result))
}

View file

@ -0,0 +1,10 @@
package uptime
import (
"net/url"
"testing"
)
func TestPoller(t *testing.T) {
Poller.Test(t, url.Values{"limit": []string{"1"}})
}

View file

@ -194,6 +194,6 @@ func (l *AccessLogger) write(data []byte) {
if err != nil { if err != nil {
l.handleErr(err) l.handleErr(err)
} else { } else {
logging.Debug().Msg("access log flushed to " + l.io.Name()) logging.Trace().Msg("access log flushed to " + l.io.Name())
} }
} }

View file

@ -2,13 +2,14 @@ package accesslog_test
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"testing" "testing"
"time" "time"
"github.com/yusing/go-proxy/pkg/json"
. "github.com/yusing/go-proxy/internal/net/gphttp/accesslog" . "github.com/yusing/go-proxy/internal/net/gphttp/accesslog"
"github.com/yusing/go-proxy/internal/task" "github.com/yusing/go-proxy/internal/task"
. "github.com/yusing/go-proxy/internal/utils/testing" . "github.com/yusing/go-proxy/internal/utils/testing"

View file

@ -2,13 +2,14 @@ package accesslog
import ( import (
"bytes" "bytes"
"encoding/json"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"time" "time"
"github.com/yusing/go-proxy/pkg/json"
"github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/logging"
) )

View file

@ -2,11 +2,12 @@ package gphttp
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"github.com/yusing/go-proxy/pkg/json"
"github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/logging"
) )

View file

@ -2,11 +2,12 @@ package gphttp
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"net/http" "net/http"
"syscall" "syscall"
"github.com/yusing/go-proxy/pkg/json"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders" "github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
) )

View file

@ -12,7 +12,6 @@ import (
"github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/net/gphttp" "github.com/yusing/go-proxy/internal/net/gphttp"
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders" "github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
"github.com/yusing/go-proxy/internal/utils"
) )
func warnNoMatchDomains() { 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) { func DynamicJSONHandler[ResultType any](w http.ResponseWriter, r *http.Request, getter func() ResultType, interval time.Duration) {
if httpheaders.IsWebsocket(r.Header) { if httpheaders.IsWebsocket(r.Header) {
Periodic(w, r, interval, func(conn *websocket.Conn) error { 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 { } else {
gphttp.RespondJSON(w, r, getter()) gphttp.RespondJSON(w, r, getter())

View file

@ -1,12 +1,13 @@
package middleware package middleware
import ( import (
"encoding/json"
"net/http" "net/http"
"reflect" "reflect"
"sort" "sort"
"strings" "strings"
"github.com/yusing/go-proxy/pkg/json"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/logging"
gphttp "github.com/yusing/go-proxy/internal/net/gphttp" gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
@ -158,12 +159,12 @@ func (m *Middleware) String() string {
return m.name return m.name
} }
func (m *Middleware) MarshalJSON() ([]byte, error) { func (m *Middleware) MarshalJSONTo(buf []byte) []byte {
return json.MarshalIndent(map[string]any{ return json.MarshalTo(map[string]any{
"name": m.name, "name": m.name,
"options": m.impl, "options": m.impl,
"priority": m.priority, "priority": m.priority,
}, "", " ") }, buf)
} }
func (m *Middleware) ModifyRequest(next http.HandlerFunc, w http.ResponseWriter, r *http.Request) { func (m *Middleware) ModifyRequest(next http.HandlerFunc, w http.ResponseWriter, r *http.Request) {

View file

@ -2,9 +2,10 @@ package middleware
import ( import (
_ "embed" _ "embed"
"encoding/json"
"testing" "testing"
"github.com/yusing/go-proxy/pkg/json"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
. "github.com/yusing/go-proxy/internal/utils/testing" . "github.com/yusing/go-proxy/internal/utils/testing"
) )
@ -16,7 +17,7 @@ func TestBuild(t *testing.T) {
errs := gperr.NewBuilder("") errs := gperr.NewBuilder("")
middlewares := BuildMiddlewaresFromYAML("", testMiddlewareCompose, errs) middlewares := BuildMiddlewaresFromYAML("", testMiddlewareCompose, errs)
ExpectNoError(t, errs.Error()) ExpectNoError(t, errs.Error())
Must(json.MarshalIndent(middlewares, "", " ")) json.Marshal(middlewares)
// t.Log(string(data)) // t.Log(string(data))
// TODO: test // TODO: test
} }

View file

@ -3,12 +3,13 @@ package middleware
import ( import (
"bytes" "bytes"
_ "embed" _ "embed"
"encoding/json"
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
"github.com/yusing/go-proxy/pkg/json"
"github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy" "github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy"

View file

@ -2,7 +2,8 @@ package notif
import ( import (
"bytes" "bytes"
"encoding/json"
"github.com/yusing/go-proxy/pkg/json"
) )
func formatMarkdown(extras LogFields) string { func formatMarkdown(extras LogFields) string {

View file

@ -2,11 +2,12 @@ package notif
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"github.com/yusing/go-proxy/pkg/json"
"github.com/gotify/server/v2/model" "github.com/gotify/server/v2/model"
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )

View file

@ -2,12 +2,13 @@ package notif
import ( import (
_ "embed" _ "embed"
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strings" "strings"
"github.com/yusing/go-proxy/pkg/json"
"github.com/yusing/go-proxy/internal/gperr" "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) { func (webhook *Webhook) MakeBody(logMsg *LogMessage) (io.Reader, error) {
title, err := json.Marshal(logMsg.Title) title := json.String(logMsg.Title)
if err != nil {
return nil, err
}
fields, err := formatDiscord(logMsg.Extras) fields, err := formatDiscord(logMsg.Extras)
if err != nil { if err != nil {
return nil, err return nil, err
@ -115,13 +113,10 @@ func (webhook *Webhook) MakeBody(logMsg *LogMessage) (io.Reader, error) {
} else { } else {
color = logMsg.Color.DecString() color = logMsg.Color.DecString()
} }
message, err := json.Marshal(formatMarkdown(logMsg.Extras)) message := json.String(formatMarkdown(logMsg.Extras))
if err != nil {
return nil, err
}
plTempl := strings.NewReplacer( plTempl := strings.NewReplacer(
"$title", string(title), "$title", title,
"$message", string(message), "$message", message,
"$fields", fields, "$fields", fields,
"$color", color, "$color", color,
) )

View file

@ -26,8 +26,8 @@ func getHealthInfo(r route.Route) map[string]string {
} }
type HealthInfoRaw struct { type HealthInfoRaw struct {
Status health.Status Status health.Status `json:"status,string"`
Latency time.Duration Latency time.Duration `json:"latency"`
} }
func getHealthInfoRaw(r route.Route) *HealthInfoRaw { func getHealthInfoRaw(r route.Route) *HealthInfoRaw {

View file

@ -300,7 +300,3 @@ func (cmd *Command) isBypass() bool {
func (cmd *Command) String() string { func (cmd *Command) String() string {
return cmd.raw return cmd.raw
} }
func (cmd *Command) MarshalText() ([]byte, error) {
return []byte(cmd.String()), nil
}

View file

@ -261,10 +261,6 @@ func (on *RuleOn) String() string {
return on.raw return on.raw
} }
func (on *RuleOn) MarshalText() ([]byte, error) {
return []byte(on.String()), nil
}
func parseOn(line string) (Checker, gperr.Error) { func parseOn(line string) (Checker, gperr.Error) {
ors := strutils.SplitRune(line, '|') ors := strutils.SplitRune(line, '|')

View file

@ -1,8 +1,9 @@
package rules package rules
import ( import (
"encoding/json"
"net/http" "net/http"
"github.com/yusing/go-proxy/pkg/json"
) )
type ( type (
@ -40,8 +41,8 @@ type (
*/ */
Rule struct { Rule struct {
Name string `json:"name"` Name string `json:"name"`
On RuleOn `json:"on"` On RuleOn `json:"on,string"`
Do Command `json:"do"` 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)) names := make([]string, len(rules))
for i, rule := range rules { for i, rule := range rules {
names[i] = rule.Name names[i] = rule.Name
} }
return json.Marshal(names) return json.MarshalTo(names, buf)
} }
func (rule *Rule) String() string { func (rule *Rule) String() string {

View file

@ -1,8 +1,9 @@
package atomic package atomic
import ( import (
"encoding/json"
"sync/atomic" "sync/atomic"
"github.com/yusing/go-proxy/pkg/json"
) )
type Value[T any] struct { type Value[T any] struct {
@ -29,6 +30,6 @@ func (a *Value[T]) Swap(v T) T {
return zero return zero
} }
func (a *Value[T]) MarshalJSON() ([]byte, error) { func (a *Value[T]) MarshalJSONTo(buf []byte) []byte {
return json.Marshal(a.Load()) return json.MarshalTo(a.Load(), buf)
} }

View file

@ -2,7 +2,6 @@ package utils
import ( import (
"bytes" "bytes"
"encoding/json"
"errors" "errors"
"net" "net"
"net/url" "net/url"
@ -12,6 +11,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/yusing/go-proxy/pkg/json"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/goccy/go-yaml" "github.com/goccy/go-yaml"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
@ -22,12 +23,15 @@ import (
type SerializedObject = map[string]any type SerializedObject = map[string]any
type ( type (
MapMarshaller interface { MapMarshaler interface {
MarshalMap() map[string]any MarshalMap() map[string]any
} }
MapUnmarshaller interface { MapUnmarshaler interface {
UnmarshalMap(m map[string]any) gperr.Error UnmarshalMap(m map[string]any) gperr.Error
} }
Marshaler interface {
MarshalJSONTo(buf *bytes.Buffer) error
}
) )
var ( var (
@ -51,11 +55,8 @@ var (
typeURL = reflect.TypeFor[url.URL]() typeURL = reflect.TypeFor[url.URL]()
typeCIDR = reflect.TypeFor[net.IPNet]() typeCIDR = reflect.TypeFor[net.IPNet]()
typeMapMarshaller = reflect.TypeFor[MapMarshaller]() typeMapUnmarshaler = reflect.TypeFor[MapUnmarshaler]()
typeMapUnmarshaler = reflect.TypeFor[MapUnmarshaller]() typeStrParser = reflect.TypeFor[strutils.Parser]()
typeJSONMarshaller = reflect.TypeFor[json.Marshaler]()
typeAny = reflect.TypeOf((*any)(nil)).Elem()
) )
var defaultValues = functional.NewMapOf[reflect.Type, func() any]() var defaultValues = functional.NewMapOf[reflect.Type, func() any]()
@ -92,14 +93,14 @@ func extractFields(t reflect.Type) (all, anonymous []reflect.StructField) {
if !field.IsExported() { if !field.IsExported() {
continue continue
} }
// not checking tagJSON because json:"-" is for skipping json.Marshal
if field.Tag.Get(tagDeserialize) == "-" { if field.Tag.Get(tagDeserialize) == "-" {
continue continue
} }
if field.Anonymous { if field.Anonymous {
f1, f2 := extractFields(field.Type) nested, _ := extractFields(field.Type)
fields = append(fields, f1...) fields = append(fields, nested...)
anonymous = append(anonymous, field) anonymous = append(anonymous, field)
anonymous = append(anonymous, f2...)
} else { } else {
fields = append(fields, field) fields = append(fields, field)
} }
@ -215,7 +216,7 @@ func MapUnmarshalValidate(src SerializedObject, dst any) (err gperr.Error) {
if err != nil { if err != nil {
return err return err
} }
return dstV.Addr().Interface().(MapUnmarshaller).UnmarshalMap(src) return dstV.Addr().Interface().(MapUnmarshaler).UnmarshalMap(src)
} }
dstV, dstT, err = dive(dstV) dstV, dstT, err = dive(dstV)

View file

@ -1,11 +1,12 @@
package monitor package monitor
import ( import (
"encoding/json"
"errors" "errors"
"net/http" "net/http"
"net/url" "net/url"
"github.com/yusing/go-proxy/pkg/json"
agentPkg "github.com/yusing/go-proxy/agent/pkg/agent" agentPkg "github.com/yusing/go-proxy/agent/pkg/agent"
"github.com/yusing/go-proxy/internal/watcher/health" "github.com/yusing/go-proxy/internal/watcher/health"
) )

View file

@ -1,6 +1,8 @@
package health package health
import "encoding/json" import (
"github.com/yusing/go-proxy/pkg/json"
)
type Status uint8 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 { func (s *Status) UnmarshalJSON(data []byte) error {
var str string var str string
if err := json.Unmarshal(data, &str); err != nil { if err := json.Unmarshal(data, &str); err != nil {

View file

@ -24,7 +24,7 @@ type (
task.TaskStarter task.TaskStarter
task.TaskFinisher task.TaskFinisher
fmt.Stringer fmt.Stringer
utils.MapMarshaller utils.MapMarshaler
WithHealthInfo WithHealthInfo
Name() string Name() string
} }