From ec6d834aae9a549b103ada521c3f034317f5faa3 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 10 Dec 2023 11:30:31 +0800 Subject: [PATCH 1/2] :sparkles: Built-in PM --- README.md | 2 +- go.mod | 1 + go.sum | 2 + pkg/administration/publish.go | 11 +-- pkg/administration/{init.go => server.go} | 0 pkg/cmd/{ => server}/main.go | 13 ++-- pkg/{fs => filesystem}/zip.go | 2 +- pkg/hypertext/proxies.go | 5 +- pkg/hypertext/{init.go => server.go} | 0 pkg/sign/configurator.go | 11 +-- pkg/sign/pm.go | 72 +++++++++++++++++++ pkg/sign/router.go | 33 ++++++--- pkg/sign/transformer.go | 7 +- pkg/sign/upstream.go | 15 ++-- test/benchmark/data/.gitignore | 1 + .../roadsign-ssr/config/example.yaml | 12 ++++ test/benchmark/roadsign-ssr/settings.yml | 26 +++++++ .../roadsign-with-prefork/config/example.json | 20 ------ .../roadsign-with-prefork/config/example.yaml | 8 +++ test/benchmark/roadsign/config/example.json | 20 ------ test/benchmark/roadsign/config/example.yaml | 8 +++ 21 files changed, 187 insertions(+), 82 deletions(-) rename pkg/administration/{init.go => server.go} (100%) rename pkg/cmd/{ => server}/main.go (96%) rename pkg/{fs => filesystem}/zip.go (98%) rename pkg/hypertext/{init.go => server.go} (100%) create mode 100644 pkg/sign/pm.go create mode 100644 test/benchmark/data/.gitignore create mode 100644 test/benchmark/roadsign-ssr/config/example.yaml create mode 100644 test/benchmark/roadsign-ssr/settings.yml delete mode 100644 test/benchmark/roadsign-with-prefork/config/example.json create mode 100644 test/benchmark/roadsign-with-prefork/config/example.yaml delete mode 100644 test/benchmark/roadsign/config/example.json create mode 100644 test/benchmark/roadsign/config/example.yaml diff --git a/README.md b/README.md index c92bcd1..46eb18b 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Here's the result: |:---------------------:|:--------------:|:--------------------:|:--------------------:|:-----------:|:------------:|:------------:|:------------:| | _Nginx_ | 515749 | 4299.58 | 2.05MB | 13.954846ms | 0s (Cached) | 410.6972ms | 0 | | _RoadSign_ | 8905230 | 76626.70 | 30.98MB | 783.016µs | 28.542µs | 46.773083ms | 0 | -| _RoadSign w/ Prefork_ | 4784308 | 40170.41 | 16.24MB | 1.493636ms | 34.291µs | 8.727666ms | 0 | +| _RoadSign w/ Prefork_ | 4784308 | 40170.41 | 16.24MB | 1.493636ms | 34.291µs | 8.727666ms | 0 | As result, roadsign undoubtedly is the fastest one. diff --git a/go.mod b/go.mod index 54b1682..64a03ae 100644 --- a/go.mod +++ b/go.mod @@ -40,5 +40,6 @@ require ( golang.org/x/sys v0.14.0 // indirect golang.org/x/text v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 78effa5..7014df3 100644 --- a/go.sum +++ b/go.sum @@ -589,6 +589,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/administration/publish.go b/pkg/administration/publish.go index dcb6d9b..a28a523 100644 --- a/pkg/administration/publish.go +++ b/pkg/administration/publish.go @@ -1,12 +1,13 @@ package administration import ( - "code.smartsheep.studio/goatworks/roadsign/pkg/fs" + "os" + "path/filepath" + + "code.smartsheep.studio/goatworks/roadsign/pkg/filesystem" "code.smartsheep.studio/goatworks/roadsign/pkg/sign" "github.com/gofiber/fiber/v2" "github.com/google/uuid" - "os" - "path/filepath" ) func doPublish(ctx *fiber.Ctx) error { @@ -15,7 +16,7 @@ func doPublish(ctx *fiber.Ctx) error { if item.ID == ctx.Params("site") { for _, stream := range item.Upstreams { if stream.ID == ctx.Params("upstream") { - upstream = &stream + upstream = stream break } } @@ -48,7 +49,7 @@ func doPublish(ctx *fiber.Ctx) error { if err := ctx.SaveFile(file, dst); err != nil { return err } else { - _ = fs.Unzip(dst, workdir) + _ = filesystem.Unzip(dst, workdir) } default: dst := filepath.Join(workdir, file.Filename) diff --git a/pkg/administration/init.go b/pkg/administration/server.go similarity index 100% rename from pkg/administration/init.go rename to pkg/administration/server.go diff --git a/pkg/cmd/main.go b/pkg/cmd/server/main.go similarity index 96% rename from pkg/cmd/main.go rename to pkg/cmd/server/main.go index df6890e..3e01b17 100644 --- a/pkg/cmd/main.go +++ b/pkg/cmd/server/main.go @@ -1,6 +1,11 @@ package main import ( + "os" + "os/signal" + "strings" + "syscall" + roadsign "code.smartsheep.studio/goatworks/roadsign/pkg" "code.smartsheep.studio/goatworks/roadsign/pkg/administration" "code.smartsheep.studio/goatworks/roadsign/pkg/hypertext" @@ -9,10 +14,6 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/spf13/viper" - "os" - "os/signal" - "strings" - "syscall" ) func init() { @@ -46,7 +47,7 @@ func main() { if err := sign.ReadInConfig(viper.GetString("paths.configs")); err != nil { log.Panic().Err(err).Msg("An error occurred when loading configurations.") } else { - log.Debug().Any("sites", sign.App).Msg("All configuration has been loaded.") + log.Info().Int("count", len(sign.App.Sites)).Msg("All configuration has been loaded.") } // Init hypertext server @@ -57,7 +58,7 @@ func main() { viper.GetString("hypertext.certificate.pem"), viper.GetString("hypertext.certificate.key"), ) - + // Init administration server hypertext.RunServer( administration.InitAdministration(), diff --git a/pkg/fs/zip.go b/pkg/filesystem/zip.go similarity index 98% rename from pkg/fs/zip.go rename to pkg/filesystem/zip.go index 4a33e0c..9e7d75f 100644 --- a/pkg/fs/zip.go +++ b/pkg/filesystem/zip.go @@ -1,4 +1,4 @@ -package fs +package filesystem import ( "archive/zip" diff --git a/pkg/hypertext/proxies.go b/pkg/hypertext/proxies.go index db3112a..5d8dbe1 100644 --- a/pkg/hypertext/proxies.go +++ b/pkg/hypertext/proxies.go @@ -1,10 +1,11 @@ package hypertext import ( + "regexp" + "code.smartsheep.studio/goatworks/roadsign/pkg/sign" "github.com/gofiber/fiber/v2" "github.com/samber/lo" - "regexp" ) func UseProxies(app *fiber.App) { @@ -88,7 +89,7 @@ func UseProxies(app *fiber.App) { }) } -func makeResponse(ctx *fiber.Ctx, site sign.SiteConfig) error { +func makeResponse(ctx *fiber.Ctx, site *sign.SiteConfig) error { // Modify request for _, transformer := range site.Transformers { transformer.TransformRequest(ctx) diff --git a/pkg/hypertext/init.go b/pkg/hypertext/server.go similarity index 100% rename from pkg/hypertext/init.go rename to pkg/hypertext/server.go diff --git a/pkg/sign/configurator.go b/pkg/sign/configurator.go index 3f8ff71..671d22c 100644 --- a/pkg/sign/configurator.go +++ b/pkg/sign/configurator.go @@ -1,18 +1,19 @@ package sign import ( - "encoding/json" "io" "os" "path/filepath" "strings" + + "gopkg.in/yaml.v2" ) var App *AppConfig func ReadInConfig(root string) error { cfg := &AppConfig{ - Sites: []SiteConfig{}, + Sites: []*SiteConfig{}, } if err := filepath.Walk(root, func(fp string, info os.FileInfo, err error) error { @@ -23,13 +24,13 @@ func ReadInConfig(root string) error { return err } else if data, err := io.ReadAll(file); err != nil { return err - } else if err := json.Unmarshal(data, &site); err != nil { + } else if err := yaml.Unmarshal(data, &site); err != nil { return err } else { // Extract file name as site id site.ID = strings.SplitN(filepath.Base(fp), ".", 2)[0] - cfg.Sites = append(cfg.Sites, site) + cfg.Sites = append(cfg.Sites, &site) } return nil @@ -44,7 +45,7 @@ func ReadInConfig(root string) error { func SaveInConfig(root string, cfg *AppConfig) error { for _, site := range cfg.Sites { - data, _ := json.Marshal(site) + data, _ := yaml.Marshal(site) fp := filepath.Join(root, site.ID) if file, err := os.OpenFile(fp, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755); err != nil { diff --git a/pkg/sign/pm.go b/pkg/sign/pm.go new file mode 100644 index 0000000..d202238 --- /dev/null +++ b/pkg/sign/pm.go @@ -0,0 +1,72 @@ +package sign + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" +) + +type ProcessConfig struct { + ID string `json:"id" yaml:"id"` + Workdir string `json:"workdir" yaml:"workdir"` + Command []string `json:"command" yaml:"command"` + Prepares [][]string `json:"prepares" yaml:"prepares"` + + Cmd *exec.Cmd `json:"-"` +} + +func (v *ProcessConfig) BootProcess() error { + if v.Cmd != nil { + return nil + } + + if err := v.PreapreProcess(); err != nil { + return err + } + if v.Cmd == nil { + return v.StartProcess() + } + if v.Cmd.Process == nil || v.Cmd.ProcessState == nil { + return v.StartProcess() + } + if v.Cmd.ProcessState.Exited() { + return v.StartProcess() + } else if v.Cmd.ProcessState.Exited() { + return fmt.Errorf("process already dead") + } + if v.Cmd.ProcessState.Exited() { + return fmt.Errorf("cannot start process") + } else { + return nil + } +} + +func (v *ProcessConfig) PreapreProcess() error { + for _, script := range v.Prepares { + if len(script) <= 0 { + continue + } + cmd := exec.Command(script[0], script[1:]...) + cmd.Dir = filepath.Join(v.Workdir) + if err := cmd.Run(); err != nil { + return err + } + } + return nil +} + +func (v *ProcessConfig) StartProcess() error { + if len(v.Command) <= 0 { + return fmt.Errorf("you need set the command for %s to enable process manager", v.ID) + } + + v.Cmd = exec.Command(v.Command[0], v.Command[1:]...) + v.Cmd.Dir = filepath.Join(v.Workdir) + + return v.Cmd.Start() +} + +func (v *ProcessConfig) StopProcess() error { + return v.Cmd.Process.Signal(os.Interrupt) +} diff --git a/pkg/sign/router.go b/pkg/sign/router.go index 3279661..e23794d 100644 --- a/pkg/sign/router.go +++ b/pkg/sign/router.go @@ -5,19 +5,29 @@ import ( "math/rand" "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog/log" ) type AppConfig struct { - Sites []SiteConfig `json:"sites"` + Sites []*SiteConfig `json:"sites"` } -func (v *AppConfig) Forward(ctx *fiber.Ctx, site SiteConfig) error { +func (v *AppConfig) Forward(ctx *fiber.Ctx, site *SiteConfig) error { if len(site.Upstreams) == 0 { return errors.New("invalid configuration") } + // Boot processes + for _, process := range site.Processes { + if err := process.BootProcess(); err != nil { + log.Warn().Err(err).Msgf("An error occurred when booting process (%s) for %s", process.ID, site.ID) + return fiber.ErrBadGateway + } + } + + // Do forward idx := rand.Intn(len(site.Upstreams)) - upstream := &site.Upstreams[idx] + upstream := site.Upstreams[idx] switch upstream.GetType() { case UpstreamTypeHypertext: @@ -30,15 +40,16 @@ func (v *AppConfig) Forward(ctx *fiber.Ctx, site SiteConfig) error { } type SiteConfig struct { - ID string `json:"id"` - Rules []RouterRuleConfig `json:"rules"` - Transformers []RequestTransformerConfig `json:"transformers"` - Upstreams []UpstreamConfig `json:"upstreams"` + ID string `json:"id"` + Rules []*RouterRuleConfig `json:"rules" yaml:"rules"` + Transformers []*RequestTransformerConfig `json:"transformers" yaml:"transformers"` + Upstreams []*UpstreamConfig `json:"upstreams" yaml:"upstreams"` + Processes []*ProcessConfig `json:"processes" yaml:"processes"` } type RouterRuleConfig struct { - Host []string `json:"host"` - Path []string `json:"path"` - Queries map[string]string `json:"query"` - Headers map[string][]string `json:"headers"` + Host []string `json:"host" yaml:"host"` + Path []string `json:"path" yaml:"path"` + Queries map[string]string `json:"queries" yaml:"queries"` + Headers map[string][]string `json:"headers" yaml:"headers"` } diff --git a/pkg/sign/transformer.go b/pkg/sign/transformer.go index 2ba9afb..20a1ebd 100644 --- a/pkg/sign/transformer.go +++ b/pkg/sign/transformer.go @@ -1,9 +1,10 @@ package sign import ( - "github.com/gofiber/fiber/v2" "regexp" "strings" + + "github.com/gofiber/fiber/v2" ) type RequestTransformer struct { @@ -12,8 +13,8 @@ type RequestTransformer struct { } type RequestTransformerConfig struct { - Type string `json:"type"` - Options any `json:"options"` + Type string `json:"type" yaml:"type"` + Options any `json:"options" yaml:"options"` } func (v *RequestTransformerConfig) TransformRequest(ctx *fiber.Ctx) { diff --git a/pkg/sign/upstream.go b/pkg/sign/upstream.go index b487bd0..09ecdf0 100644 --- a/pkg/sign/upstream.go +++ b/pkg/sign/upstream.go @@ -2,10 +2,11 @@ package sign import ( "fmt" - "github.com/gofiber/fiber/v2" - "github.com/samber/lo" "net/url" "strings" + + "github.com/gofiber/fiber/v2" + "github.com/samber/lo" ) const ( @@ -15,18 +16,16 @@ const ( ) type UpstreamConfig struct { - ID string `json:"id"` - URI string `json:"uri"` + ID string `json:"id" yaml:"id"` + URI string `json:"uri" yaml:"uri"` } func (v *UpstreamConfig) GetType() string { protocol := strings.SplitN(v.URI, "://", 2)[0] switch protocol { - case "file": - case "files": + case "file", "files": return UpstreamTypeFile - case "http": - case "https": + case "http", "https": return UpstreamTypeHypertext } diff --git a/test/benchmark/data/.gitignore b/test/benchmark/data/.gitignore new file mode 100644 index 0000000..a5410f1 --- /dev/null +++ b/test/benchmark/data/.gitignore @@ -0,0 +1 @@ +/.output \ No newline at end of file diff --git a/test/benchmark/roadsign-ssr/config/example.yaml b/test/benchmark/roadsign-ssr/config/example.yaml new file mode 100644 index 0000000..8af1f24 --- /dev/null +++ b/test/benchmark/roadsign-ssr/config/example.yaml @@ -0,0 +1,12 @@ +name: Example Site +rules: + - host: ["localhost:8000"] + path: ["/"] +upstreams: + - id: example + name: Benchmarking Data + uri: http://localhost:3000 +processes: + - id: nuxt-ssr + workdir: ../data + command: ["node", ".output/server/index.mjs"] diff --git a/test/benchmark/roadsign-ssr/settings.yml b/test/benchmark/roadsign-ssr/settings.yml new file mode 100644 index 0000000..8e5bdd2 --- /dev/null +++ b/test/benchmark/roadsign-ssr/settings.yml @@ -0,0 +1,26 @@ +debug: + print_routes: false +hypertext: + administration_ports: [] + administration_secured_ports: [] + certificate: + administration_key: ./cert.key + administration_pem: ./cert.pem + key: ./cert.key + pem: ./cert.pem + limitation: + max_body_size: -1 + max_qps: -1 + ports: + - :8000 + secured_ports: [] +paths: + configs: ./config +performance: + request_logging: false + network_timeout: 3000 + prefork: false +security: + administration_trusted_proxies: + - localhost + credential: e81f43f32d934271af6322e5376f5f59 diff --git a/test/benchmark/roadsign-with-prefork/config/example.json b/test/benchmark/roadsign-with-prefork/config/example.json deleted file mode 100644 index 9c3bc7b..0000000 --- a/test/benchmark/roadsign-with-prefork/config/example.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Example Site", - "rules": [ - { - "host": [ - "localhost:8000" - ], - "path": [ - "/" - ] - } - ], - "upstreams": [ - { - "id": "example", - "name": "Benchmarking Data", - "uri": "files://../data" - } - ] -} \ No newline at end of file diff --git a/test/benchmark/roadsign-with-prefork/config/example.yaml b/test/benchmark/roadsign-with-prefork/config/example.yaml new file mode 100644 index 0000000..2a46d7d --- /dev/null +++ b/test/benchmark/roadsign-with-prefork/config/example.yaml @@ -0,0 +1,8 @@ +name: Example Site +rules: + - host: ["localhost:8000"] + path: ["/"] +upstreams: + - id: example + name: Benchmarking Data + uri: files://../data diff --git a/test/benchmark/roadsign/config/example.json b/test/benchmark/roadsign/config/example.json deleted file mode 100644 index 9c3bc7b..0000000 --- a/test/benchmark/roadsign/config/example.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Example Site", - "rules": [ - { - "host": [ - "localhost:8000" - ], - "path": [ - "/" - ] - } - ], - "upstreams": [ - { - "id": "example", - "name": "Benchmarking Data", - "uri": "files://../data" - } - ] -} \ No newline at end of file diff --git a/test/benchmark/roadsign/config/example.yaml b/test/benchmark/roadsign/config/example.yaml new file mode 100644 index 0000000..2a46d7d --- /dev/null +++ b/test/benchmark/roadsign/config/example.yaml @@ -0,0 +1,8 @@ +name: Example Site +rules: + - host: ["localhost:8000"] + path: ["/"] +upstreams: + - id: example + name: Benchmarking Data + uri: files://../data -- 2.45.2 From 9aeaf0b1e242e6e1269e1c13188b68b616c7dbfc Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 10 Dec 2023 11:54:28 +0800 Subject: [PATCH 2/2] :green_heart: Fix dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index af9260b..5f2e8e9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM golang:alpine as roadsign-server WORKDIR /source COPY . . -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /dist ./pkg/cmd/main.go +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /dist ./pkg/cmd/server/main.go # Runtime FROM golang:alpine -- 2.45.2