🔥 重构 YAML 配置文件 #2

Merged
LittleSheep merged 2 commits from refactor/new-configuration into master 2023-12-10 03:56:36 +00:00
22 changed files with 188 additions and 83 deletions

View File

@ -3,7 +3,7 @@ FROM golang:alpine as roadsign-server
WORKDIR /source WORKDIR /source
COPY . . 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 # Runtime
FROM golang:alpine FROM golang:alpine

View File

@ -23,7 +23,7 @@ Here's the result:
|:---------------------:|:--------------:|:--------------------:|:--------------------:|:-----------:|:------------:|:------------:|:------------:| |:---------------------:|:--------------:|:--------------------:|:--------------------:|:-----------:|:------------:|:------------:|:------------:|
| _Nginx_ | 515749 | 4299.58 | 2.05MB | 13.954846ms | 0s (Cached) | 410.6972ms | 0 | | _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_ | 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. As result, roadsign undoubtedly is the fastest one.

1
go.mod
View File

@ -40,5 +40,6 @@ require (
golang.org/x/sys v0.14.0 // indirect golang.org/x/sys v0.14.0 // indirect
golang.org/x/text v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect
gopkg.in/ini.v1 v1.67.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 gopkg.in/yaml.v3 v3.0.1 // indirect
) )

2
go.sum
View File

@ -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 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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.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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,12 +1,13 @@
package administration package administration
import ( 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" "code.smartsheep.studio/goatworks/roadsign/pkg/sign"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/google/uuid" "github.com/google/uuid"
"os"
"path/filepath"
) )
func doPublish(ctx *fiber.Ctx) error { func doPublish(ctx *fiber.Ctx) error {
@ -15,7 +16,7 @@ func doPublish(ctx *fiber.Ctx) error {
if item.ID == ctx.Params("site") { if item.ID == ctx.Params("site") {
for _, stream := range item.Upstreams { for _, stream := range item.Upstreams {
if stream.ID == ctx.Params("upstream") { if stream.ID == ctx.Params("upstream") {
upstream = &stream upstream = stream
break break
} }
} }
@ -48,7 +49,7 @@ func doPublish(ctx *fiber.Ctx) error {
if err := ctx.SaveFile(file, dst); err != nil { if err := ctx.SaveFile(file, dst); err != nil {
return err return err
} else { } else {
_ = fs.Unzip(dst, workdir) _ = filesystem.Unzip(dst, workdir)
} }
default: default:
dst := filepath.Join(workdir, file.Filename) dst := filepath.Join(workdir, file.Filename)

View File

@ -1,6 +1,11 @@
package main package main
import ( import (
"os"
"os/signal"
"strings"
"syscall"
roadsign "code.smartsheep.studio/goatworks/roadsign/pkg" roadsign "code.smartsheep.studio/goatworks/roadsign/pkg"
"code.smartsheep.studio/goatworks/roadsign/pkg/administration" "code.smartsheep.studio/goatworks/roadsign/pkg/administration"
"code.smartsheep.studio/goatworks/roadsign/pkg/hypertext" "code.smartsheep.studio/goatworks/roadsign/pkg/hypertext"
@ -9,10 +14,6 @@ import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/viper" "github.com/spf13/viper"
"os"
"os/signal"
"strings"
"syscall"
) )
func init() { func init() {
@ -46,7 +47,7 @@ func main() {
if err := sign.ReadInConfig(viper.GetString("paths.configs")); err != nil { if err := sign.ReadInConfig(viper.GetString("paths.configs")); err != nil {
log.Panic().Err(err).Msg("An error occurred when loading configurations.") log.Panic().Err(err).Msg("An error occurred when loading configurations.")
} else { } 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 // Init hypertext server

View File

@ -1,4 +1,4 @@
package fs package filesystem
import ( import (
"archive/zip" "archive/zip"

View File

@ -1,10 +1,11 @@
package hypertext package hypertext
import ( import (
"regexp"
"code.smartsheep.studio/goatworks/roadsign/pkg/sign" "code.smartsheep.studio/goatworks/roadsign/pkg/sign"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/samber/lo" "github.com/samber/lo"
"regexp"
) )
func UseProxies(app *fiber.App) { 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 // Modify request
for _, transformer := range site.Transformers { for _, transformer := range site.Transformers {
transformer.TransformRequest(ctx) transformer.TransformRequest(ctx)

View File

@ -1,18 +1,19 @@
package sign package sign
import ( import (
"encoding/json"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"gopkg.in/yaml.v2"
) )
var App *AppConfig var App *AppConfig
func ReadInConfig(root string) error { func ReadInConfig(root string) error {
cfg := &AppConfig{ cfg := &AppConfig{
Sites: []SiteConfig{}, Sites: []*SiteConfig{},
} }
if err := filepath.Walk(root, func(fp string, info os.FileInfo, err error) error { 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 return err
} else if data, err := io.ReadAll(file); err != nil { } else if data, err := io.ReadAll(file); err != nil {
return err return err
} else if err := json.Unmarshal(data, &site); err != nil { } else if err := yaml.Unmarshal(data, &site); err != nil {
return err return err
} else { } else {
// Extract file name as site id // Extract file name as site id
site.ID = strings.SplitN(filepath.Base(fp), ".", 2)[0] site.ID = strings.SplitN(filepath.Base(fp), ".", 2)[0]
cfg.Sites = append(cfg.Sites, site) cfg.Sites = append(cfg.Sites, &site)
} }
return nil return nil
@ -44,7 +45,7 @@ func ReadInConfig(root string) error {
func SaveInConfig(root string, cfg *AppConfig) error { func SaveInConfig(root string, cfg *AppConfig) error {
for _, site := range cfg.Sites { for _, site := range cfg.Sites {
data, _ := json.Marshal(site) data, _ := yaml.Marshal(site)
fp := filepath.Join(root, site.ID) fp := filepath.Join(root, site.ID)
if file, err := os.OpenFile(fp, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755); err != nil { if file, err := os.OpenFile(fp, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755); err != nil {

72
pkg/sign/pm.go Normal file
View File

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

View File

@ -5,19 +5,29 @@ import (
"math/rand" "math/rand"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/rs/zerolog/log"
) )
type AppConfig struct { 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 { if len(site.Upstreams) == 0 {
return errors.New("invalid configuration") 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)) idx := rand.Intn(len(site.Upstreams))
upstream := &site.Upstreams[idx] upstream := site.Upstreams[idx]
switch upstream.GetType() { switch upstream.GetType() {
case UpstreamTypeHypertext: case UpstreamTypeHypertext:
@ -30,15 +40,16 @@ func (v *AppConfig) Forward(ctx *fiber.Ctx, site SiteConfig) error {
} }
type SiteConfig struct { type SiteConfig struct {
ID string `json:"id"` ID string `json:"id"`
Rules []RouterRuleConfig `json:"rules"` Rules []*RouterRuleConfig `json:"rules" yaml:"rules"`
Transformers []RequestTransformerConfig `json:"transformers"` Transformers []*RequestTransformerConfig `json:"transformers" yaml:"transformers"`
Upstreams []UpstreamConfig `json:"upstreams"` Upstreams []*UpstreamConfig `json:"upstreams" yaml:"upstreams"`
Processes []*ProcessConfig `json:"processes" yaml:"processes"`
} }
type RouterRuleConfig struct { type RouterRuleConfig struct {
Host []string `json:"host"` Host []string `json:"host" yaml:"host"`
Path []string `json:"path"` Path []string `json:"path" yaml:"path"`
Queries map[string]string `json:"query"` Queries map[string]string `json:"queries" yaml:"queries"`
Headers map[string][]string `json:"headers"` Headers map[string][]string `json:"headers" yaml:"headers"`
} }

View File

@ -1,9 +1,10 @@
package sign package sign
import ( import (
"github.com/gofiber/fiber/v2"
"regexp" "regexp"
"strings" "strings"
"github.com/gofiber/fiber/v2"
) )
type RequestTransformer struct { type RequestTransformer struct {
@ -12,8 +13,8 @@ type RequestTransformer struct {
} }
type RequestTransformerConfig struct { type RequestTransformerConfig struct {
Type string `json:"type"` Type string `json:"type" yaml:"type"`
Options any `json:"options"` Options any `json:"options" yaml:"options"`
} }
func (v *RequestTransformerConfig) TransformRequest(ctx *fiber.Ctx) { func (v *RequestTransformerConfig) TransformRequest(ctx *fiber.Ctx) {

View File

@ -2,10 +2,11 @@ package sign
import ( import (
"fmt" "fmt"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
"net/url" "net/url"
"strings" "strings"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
) )
const ( const (
@ -15,18 +16,16 @@ const (
) )
type UpstreamConfig struct { type UpstreamConfig struct {
ID string `json:"id"` ID string `json:"id" yaml:"id"`
URI string `json:"uri"` URI string `json:"uri" yaml:"uri"`
} }
func (v *UpstreamConfig) GetType() string { func (v *UpstreamConfig) GetType() string {
protocol := strings.SplitN(v.URI, "://", 2)[0] protocol := strings.SplitN(v.URI, "://", 2)[0]
switch protocol { switch protocol {
case "file": case "file", "files":
case "files":
return UpstreamTypeFile return UpstreamTypeFile
case "http": case "http", "https":
case "https":
return UpstreamTypeHypertext return UpstreamTypeHypertext
} }

1
test/benchmark/data/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/.output

View File

@ -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"]

View File

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

View File

@ -1,20 +0,0 @@
{
"name": "Example Site",
"rules": [
{
"host": [
"localhost:8000"
],
"path": [
"/"
]
}
],
"upstreams": [
{
"id": "example",
"name": "Benchmarking Data",
"uri": "files://../data"
}
]
}

View File

@ -0,0 +1,8 @@
name: Example Site
rules:
- host: ["localhost:8000"]
path: ["/"]
upstreams:
- id: example
name: Benchmarking Data
uri: files://../data

View File

@ -1,20 +0,0 @@
{
"name": "Example Site",
"rules": [
{
"host": [
"localhost:8000"
],
"path": [
"/"
]
}
],
"upstreams": [
{
"id": "example",
"name": "Benchmarking Data",
"uri": "files://../data"
}
]
}

View File

@ -0,0 +1,8 @@
name: Example Site
rules:
- host: ["localhost:8000"]
path: ["/"]
upstreams:
- id: example
name: Benchmarking Data
uri: files://../data