✨ Uses v2 configration schema
This commit is contained in:
@ -4,34 +4,35 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
var App *RoadApp
|
||||
var R *RoadApp
|
||||
|
||||
func ReadInConfig(root string) error {
|
||||
instance := &RoadApp{
|
||||
Sites: []*SiteConfig{},
|
||||
Regions: make([]*Region, 0),
|
||||
}
|
||||
|
||||
if err := filepath.Walk(root, func(fp string, info os.FileInfo, err error) error {
|
||||
var site SiteConfig
|
||||
if err := filepath.Walk(root, func(fp string, info os.FileInfo, _ error) error {
|
||||
var region Region
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
} else if file, err := os.OpenFile(fp, os.O_RDONLY, 0755); err != nil {
|
||||
return err
|
||||
} else if data, err := io.ReadAll(file); err != nil {
|
||||
return err
|
||||
} else if err := yaml.Unmarshal(data, &site); err != nil {
|
||||
} else if err := toml.Unmarshal(data, ®ion); err != nil {
|
||||
return err
|
||||
} else {
|
||||
defer file.Close()
|
||||
|
||||
// Extract file name as site id
|
||||
site.ID = strings.SplitN(filepath.Base(fp), ".", 2)[0]
|
||||
instance.Sites = append(instance.Sites, &site)
|
||||
if region.Disabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
instance.Regions = append(instance.Regions, ®ion)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -39,7 +40,7 @@ func ReadInConfig(root string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
App = instance
|
||||
R = instance
|
||||
|
||||
return nil
|
||||
}
|
@ -18,16 +18,16 @@ import (
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
func makeHypertextResponse(c *fiber.Ctx, upstream *UpstreamInstance) error {
|
||||
func makeHypertextResponse(c *fiber.Ctx, dest *Destination) error {
|
||||
timeout := time.Duration(viper.GetInt64("performance.network_timeout")) * time.Millisecond
|
||||
return proxy.Do(c, upstream.MakeURI(c), &fasthttp.Client{
|
||||
return proxy.Do(c, dest.MakeUri(c), &fasthttp.Client{
|
||||
ReadTimeout: timeout,
|
||||
WriteTimeout: timeout,
|
||||
})
|
||||
}
|
||||
|
||||
func makeFileResponse(c *fiber.Ctx, upstream *UpstreamInstance) error {
|
||||
uri, queries := upstream.GetRawURI()
|
||||
func makeFileResponse(c *fiber.Ctx, dest *Destination) error {
|
||||
uri, queries := dest.GetRawUri()
|
||||
root := http.Dir(uri)
|
||||
|
||||
method := c.Method()
|
||||
|
@ -1,49 +1,24 @@
|
||||
package navi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
|
||||
"code.smartsheep.studio/goatworks/roadsign/pkg/navi/transformers"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type RoadApp struct {
|
||||
Sites []*SiteConfig `json:"sites"`
|
||||
Regions []*Region `json:"regions"`
|
||||
}
|
||||
|
||||
func (v *RoadApp) Forward(ctx *fiber.Ctx, site *SiteConfig) error {
|
||||
if len(site.Upstreams) == 0 {
|
||||
return errors.New("invalid configuration")
|
||||
}
|
||||
|
||||
// Do forward
|
||||
idx := rand.Intn(len(site.Upstreams))
|
||||
upstream := site.Upstreams[idx]
|
||||
|
||||
switch upstream.GetType() {
|
||||
case UpstreamTypeHypertext:
|
||||
return makeHypertextResponse(ctx, upstream)
|
||||
case UpstreamTypeFile:
|
||||
return makeFileResponse(ctx, upstream)
|
||||
func (v *RoadApp) Forward(ctx *fiber.Ctx, dest *Destination) error {
|
||||
switch dest.GetType() {
|
||||
case DestinationHypertext:
|
||||
return makeHypertextResponse(ctx, dest)
|
||||
case DestinationStaticFile:
|
||||
return makeFileResponse(ctx, dest)
|
||||
default:
|
||||
return fiber.ErrBadGateway
|
||||
}
|
||||
}
|
||||
|
||||
type RequestTransformerConfig = transformers.RequestTransformerConfig
|
||||
|
||||
type SiteConfig struct {
|
||||
ID string `json:"id"`
|
||||
Rules []*RouterRule `json:"rules" yaml:"rules"`
|
||||
Transformers []*RequestTransformerConfig `json:"transformers" yaml:"transformers"`
|
||||
Upstreams []*UpstreamInstance `json:"upstreams" yaml:"upstreams"`
|
||||
}
|
||||
|
||||
type RouterRule struct {
|
||||
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"`
|
||||
}
|
||||
type RequestTransformerConfig = transformers.TransformerConfig
|
||||
|
78
pkg/navi/struct.go
Normal file
78
pkg/navi/struct.go
Normal file
@ -0,0 +1,78 @@
|
||||
package navi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.smartsheep.studio/goatworks/roadsign/pkg/navi/transformers"
|
||||
"code.smartsheep.studio/goatworks/roadsign/pkg/warden"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type Region struct {
|
||||
ID string `json:"id" toml:"id"`
|
||||
Disabled bool `json:"disabled" toml:"disabled"`
|
||||
Locations []Location `json:"locations" toml:"locations"`
|
||||
Applications []warden.Application `json:"applications" toml:"applications"`
|
||||
}
|
||||
|
||||
type Location struct {
|
||||
ID string `json:"id" toml:"id"`
|
||||
Host []string `json:"host" toml:"host"`
|
||||
Path []string `json:"path" toml:"path"`
|
||||
Queries map[string]string `json:"queries" toml:"queries"`
|
||||
Headers map[string][]string `json:"headers" toml:"headers"`
|
||||
Destinations []Destination `json:"destinations" toml:"destinations"`
|
||||
}
|
||||
|
||||
type DestinationType = int8
|
||||
|
||||
const (
|
||||
DestinationHypertext = DestinationType(iota)
|
||||
DestinationStaticFile
|
||||
DestinationUnknown
|
||||
)
|
||||
|
||||
type Destination struct {
|
||||
ID string `json:"id" toml:"id"`
|
||||
Uri string `json:"uri" toml:"uri"`
|
||||
Transformers []transformers.TransformerConfig `json:"transformers" toml:"transformers"`
|
||||
}
|
||||
|
||||
func (v *Destination) GetType() DestinationType {
|
||||
protocol := strings.SplitN(v.Uri, "://", 2)[0]
|
||||
switch protocol {
|
||||
case "http", "https":
|
||||
return DestinationHypertext
|
||||
case "file", "files":
|
||||
return DestinationStaticFile
|
||||
}
|
||||
return DestinationUnknown
|
||||
}
|
||||
|
||||
func (v *Destination) GetRawUri() (string, url.Values) {
|
||||
uri := strings.SplitN(v.Uri, "://", 2)[1]
|
||||
data := strings.SplitN(uri, "?", 2)
|
||||
data = append(data, " ") // Make data array least have two element
|
||||
qs, _ := url.ParseQuery(data[0])
|
||||
|
||||
return data[0], qs
|
||||
}
|
||||
|
||||
func (v *Destination) MakeUri(ctx *fiber.Ctx) string {
|
||||
var queries []string
|
||||
for k, v := range ctx.Queries() {
|
||||
parsed, _ := url.QueryUnescape(v)
|
||||
value := url.QueryEscape(parsed)
|
||||
queries = append(queries, fmt.Sprintf("%s=%s", k, value))
|
||||
}
|
||||
|
||||
path := string(ctx.Request().URI().Path())
|
||||
hash := string(ctx.Request().URI().Hash())
|
||||
|
||||
return v.Uri + path +
|
||||
lo.Ternary(len(queries) > 0, "?"+strings.Join(queries, "&"), "") +
|
||||
lo.Ternary(len(hash) > 0, "#"+hash, "")
|
||||
}
|
@ -5,13 +5,13 @@ import (
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
var CompressResponse = RequestTransformer{
|
||||
var CompressResponse = Transformer{
|
||||
ModifyResponse: func(options any, ctx *fiber.Ctx) error {
|
||||
opts := DeserializeOptions[struct {
|
||||
Level int `json:"level" yaml:"level"`
|
||||
Level int `json:"level" toml:"level"`
|
||||
}](options)
|
||||
|
||||
var fctx = func(c *fasthttp.RequestCtx) {}
|
||||
fctx := func(c *fasthttp.RequestCtx) {}
|
||||
var compressor fasthttp.RequestHandler
|
||||
switch opts.Level {
|
||||
// Best Speed Mode
|
||||
|
@ -9,17 +9,17 @@ import (
|
||||
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
|
||||
type RequestTransformer struct {
|
||||
type Transformer struct {
|
||||
ModifyRequest func(options any, ctx *fiber.Ctx) error
|
||||
ModifyResponse func(options any, ctx *fiber.Ctx) error
|
||||
}
|
||||
|
||||
type RequestTransformerConfig struct {
|
||||
Type string `json:"type" yaml:"type"`
|
||||
Options any `json:"options" yaml:"options"`
|
||||
type TransformerConfig struct {
|
||||
Type string `json:"type" toml:"type"`
|
||||
Options any `json:"options" toml:"options"`
|
||||
}
|
||||
|
||||
func (v *RequestTransformerConfig) TransformRequest(ctx *fiber.Ctx) error {
|
||||
func (v *TransformerConfig) TransformRequest(ctx *fiber.Ctx) error {
|
||||
for k, f := range Transformers {
|
||||
if k == v.Type {
|
||||
if f.ModifyRequest != nil {
|
||||
@ -31,7 +31,7 @@ func (v *RequestTransformerConfig) TransformRequest(ctx *fiber.Ctx) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *RequestTransformerConfig) TransformResponse(ctx *fiber.Ctx) error {
|
||||
func (v *TransformerConfig) TransformResponse(ctx *fiber.Ctx) error {
|
||||
for k, f := range Transformers {
|
||||
if k == v.Type {
|
||||
if f.ModifyResponse != nil {
|
||||
@ -55,7 +55,7 @@ func DeserializeOptions[T any](data any) T {
|
||||
// Map of Transformers
|
||||
// Every transformer need to be mapped here so that they can get work.
|
||||
|
||||
var Transformers = map[string]RequestTransformer{
|
||||
var Transformers = map[string]Transformer{
|
||||
"replacePath": ReplacePath,
|
||||
"compressResponse": CompressResponse,
|
||||
}
|
||||
|
@ -1,18 +1,19 @@
|
||||
package transformers
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
var ReplacePath = RequestTransformer{
|
||||
var ReplacePath = Transformer{
|
||||
ModifyRequest: func(options any, ctx *fiber.Ctx) error {
|
||||
opts := DeserializeOptions[struct {
|
||||
Pattern string `json:"pattern" yaml:"pattern"`
|
||||
Value string `json:"value" yaml:"value"`
|
||||
Repl string `json:"repl" yaml:"repl"` // Use when complex mode(regexp) enabled
|
||||
Complex bool `json:"complex" yaml:"complex"`
|
||||
Pattern string `json:"pattern" toml:"pattern"`
|
||||
Value string `json:"value" toml:"value"`
|
||||
Repl string `json:"repl" toml:"repl"` // Use when complex mode(regexp) enabled
|
||||
Complex bool `json:"complex" toml:"complex"`
|
||||
}](options)
|
||||
path := string(ctx.Request().URI().Path())
|
||||
if !opts.Complex {
|
||||
|
@ -1,58 +0,0 @@
|
||||
package navi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
const (
|
||||
UpstreamTypeFile = "file"
|
||||
UpstreamTypeHypertext = "hypertext"
|
||||
UpstreamTypeUnknown = "unknown"
|
||||
)
|
||||
|
||||
type UpstreamInstance struct {
|
||||
ID string `json:"id" yaml:"id"`
|
||||
URI string `json:"uri" yaml:"uri"`
|
||||
}
|
||||
|
||||
func (v *UpstreamInstance) GetType() string {
|
||||
protocol := strings.SplitN(v.URI, "://", 2)[0]
|
||||
switch protocol {
|
||||
case "file", "files":
|
||||
return UpstreamTypeFile
|
||||
case "http", "https":
|
||||
return UpstreamTypeHypertext
|
||||
}
|
||||
|
||||
return UpstreamTypeUnknown
|
||||
}
|
||||
|
||||
func (v *UpstreamInstance) GetRawURI() (string, url.Values) {
|
||||
uri := strings.SplitN(v.URI, "://", 2)[1]
|
||||
data := strings.SplitN(uri, "?", 2)
|
||||
data = append(data, " ") // Make data array least have two element
|
||||
qs, _ := url.ParseQuery(data[0])
|
||||
|
||||
return data[0], qs
|
||||
}
|
||||
|
||||
func (v *UpstreamInstance) MakeURI(ctx *fiber.Ctx) string {
|
||||
var queries []string
|
||||
for k, v := range ctx.Queries() {
|
||||
parsed, _ := url.QueryUnescape(v)
|
||||
value := url.QueryEscape(parsed)
|
||||
queries = append(queries, fmt.Sprintf("%s=%s", k, value))
|
||||
}
|
||||
|
||||
path := string(ctx.Request().URI().Path())
|
||||
hash := string(ctx.Request().URI().Hash())
|
||||
|
||||
return v.URI + path +
|
||||
lo.Ternary(len(queries) > 0, "?"+strings.Join(queries, "&"), "") +
|
||||
lo.Ternary(len(hash) > 0, "#"+hash, "")
|
||||
}
|
17
pkg/navi/warden.go
Normal file
17
pkg/navi/warden.go
Normal file
@ -0,0 +1,17 @@
|
||||
package navi
|
||||
|
||||
import "code.smartsheep.studio/goatworks/roadsign/pkg/warden"
|
||||
|
||||
func InitializeWarden(regions []*Region) {
|
||||
for _, region := range regions {
|
||||
for _, application := range region.Applications {
|
||||
warden.InstancePool = append(warden.InstancePool, &warden.AppInstance{
|
||||
Manifest: application,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, instance := range warden.InstancePool {
|
||||
instance.Wake()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user