diff --git a/.idea/RoadSign.iml b/.idea/RoadSign.iml
index 9dda0dd..15a54e1 100644
--- a/.idea/RoadSign.iml
+++ b/.idea/RoadSign.iml
@@ -11,5 +11,6 @@
+
\ No newline at end of file
diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml
new file mode 100644
index 0000000..31191ee
--- /dev/null
+++ b/.idea/jsLibraryMappings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 0aada8f..93cb3d9 100644
--- a/go.mod
+++ b/go.mod
@@ -17,6 +17,9 @@ require (
require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
+ github.com/gofiber/template v1.8.2 // indirect
+ github.com/gofiber/template/html/v2 v2.1.0 // indirect
+ github.com/gofiber/utils v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
diff --git a/go.sum b/go.sum
index e834ea5..3febcd8 100644
--- a/go.sum
+++ b/go.sum
@@ -16,6 +16,12 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofiber/fiber/v2 v2.52.0 h1:S+qXi7y+/Pgvqq4DrSmREGiFwtB7Bu6+QFLuIHYw/UE=
github.com/gofiber/fiber/v2 v2.52.0/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
+github.com/gofiber/template v1.8.2 h1:PIv9s/7Uq6m+Fm2MDNd20pAFFKt5wWs7ZBd8iV9pWwk=
+github.com/gofiber/template v1.8.2/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
+github.com/gofiber/template/html/v2 v2.1.0 h1:FjwzqhhdJpnhyCvav60Z1ytnBqOUr5sGO/aTeob9/ng=
+github.com/gofiber/template/html/v2 v2.1.0/go.mod h1:txXsRQN/G7Fr2cqGfr6zhVHgreCfpsBS+9+DJyrddJc=
+github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
+github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
diff --git a/pkg/hypertext/proxies.go b/pkg/hypertext/proxies.go
index 3b12566..e71d921 100644
--- a/pkg/hypertext/proxies.go
+++ b/pkg/hypertext/proxies.go
@@ -10,88 +10,86 @@ import (
"github.com/samber/lo"
)
-func UseProxies(app *fiber.App) {
- app.All("/*", func(ctx *fiber.Ctx) error {
- host := ctx.Hostname()
- path := ctx.Path()
- queries := ctx.Queries()
- headers := ctx.GetReqHeaders()
+func ProxiesHandler(ctx *fiber.Ctx) error {
+ host := ctx.Hostname()
+ path := ctx.Path()
+ queries := ctx.Queries()
+ headers := ctx.GetReqHeaders()
- // Filtering sites
- for _, region := range navi.R.Regions {
- // Matching rules
- for _, location := range region.Locations {
- if !lo.Contains(location.Host, host) {
- continue
- }
+ // Filtering sites
+ for _, region := range navi.R.Regions {
+ // Matching rules
+ for _, location := range region.Locations {
+ if !lo.Contains(location.Host, host) {
+ continue
+ }
- if !func() bool {
- flag := false
- for _, pattern := range location.Path {
- if ok, _ := regexp.MatchString(pattern, path); ok {
- flag = true
- break
- }
+ if !func() bool {
+ flag := false
+ for _, pattern := range location.Path {
+ if ok, _ := regexp.MatchString(pattern, path); ok {
+ flag = true
+ break
}
- return flag
- }() {
- continue
}
+ return flag
+ }() {
+ continue
+ }
- // Filter query strings
- flag := true
- for rk, rv := range location.Queries {
- for ik, iv := range queries {
- if rk != ik && rv != iv {
- flag = false
- break
- }
- }
- if !flag {
+ // Filter query strings
+ flag := true
+ for rk, rv := range location.Queries {
+ for ik, iv := range queries {
+ if rk != ik && rv != iv {
+ flag = false
break
}
}
if !flag {
- continue
+ break
}
+ }
+ if !flag {
+ continue
+ }
- // Filter headers
- for rk, rv := range location.Headers {
- for ik, iv := range headers {
- if rk == ik {
- for _, ov := range iv {
- if !lo.Contains(rv, ov) {
- flag = false
- break
- }
+ // Filter headers
+ for rk, rv := range location.Headers {
+ for ik, iv := range headers {
+ if rk == ik {
+ for _, ov := range iv {
+ if !lo.Contains(rv, ov) {
+ flag = false
+ break
}
}
- if !flag {
- break
- }
}
if !flag {
break
}
}
if !flag {
- continue
+ break
}
-
- idx := rand.Intn(len(location.Destinations))
- dest := location.Destinations[idx]
-
- // Passing all the rules means the site is what we are looking for.
- // Let us respond to our client!
- return makeResponse(ctx, region, &location, &dest)
}
- }
+ if !flag {
+ continue
+ }
- // There is no site available for this request.
- // Just ignore it and give our client a not found status.
- // Do not care about the user experience, we can do it in custom error handler.
- return fiber.ErrNotFound
- })
+ idx := rand.Intn(len(location.Destinations))
+ dest := location.Destinations[idx]
+
+ // Passing all the rules means the site is what we are looking for.
+ // Let us respond to our client!
+ return makeResponse(ctx, region, &location, &dest)
+ }
+ }
+
+ // There is no site available for this request.
+ // Just ignore it and give our client a not found status.
+ // Do not care about the user experience, we can do it in custom error handler.
+ return fiber.ErrNotFound
}
func makeResponse(c *fiber.Ctx, region *navi.Region, location *navi.Location, dest *navi.Destination) error {
diff --git a/pkg/hypertext/server.go b/pkg/hypertext/server.go
index d153994..27682f9 100644
--- a/pkg/hypertext/server.go
+++ b/pkg/hypertext/server.go
@@ -1,25 +1,32 @@
package hypertext
import (
+ "code.smartsheep.studio/goatworks/roadsign/pkg/hypertext/status"
"crypto/tls"
jsoniter "github.com/json-iterator/go"
"net"
+ "net/http"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/limiter"
"github.com/gofiber/fiber/v2/middleware/logger"
+ "github.com/gofiber/template/html/v2"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
)
func InitServer() *fiber.App {
+ views := html.NewFileSystem(http.FS(status.FS), ".gohtml")
app := fiber.New(fiber.Config{
+ ViewsLayout: "views/index",
AppName: "RoadSign",
ServerHeader: "RoadSign",
DisableStartupMessage: true,
EnableIPValidation: true,
+ Views: views,
+ ErrorHandler: status.StatusPageHandler,
JSONDecoder: jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal,
JSONEncoder: jsoniter.ConfigCompatibleWithStandardLibrary.Marshal,
Prefork: viper.GetBool("performance.prefork"),
@@ -50,10 +57,13 @@ func InitServer() *fiber.App {
app.Use(limiter.New(limiter.Config{
Max: viper.GetInt("hypertext.limitation.max_qps"),
Expiration: 1 * time.Second,
+ LimitReached: func(c *fiber.Ctx) error {
+ return fiber.ErrTooManyRequests
+ },
}))
}
- UseProxies(app)
+ app.All("/*", ProxiesHandler)
return app
}
diff --git a/pkg/hypertext/status/embed.go b/pkg/hypertext/status/embed.go
new file mode 100644
index 0000000..14c2898
--- /dev/null
+++ b/pkg/hypertext/status/embed.go
@@ -0,0 +1,6 @@
+package status
+
+import "embed"
+
+//go:embed all:views
+var FS embed.FS
diff --git a/pkg/hypertext/status/serve.go b/pkg/hypertext/status/serve.go
new file mode 100644
index 0000000..7118ef4
--- /dev/null
+++ b/pkg/hypertext/status/serve.go
@@ -0,0 +1,56 @@
+package status
+
+import (
+ roadsign "code.smartsheep.studio/goatworks/roadsign/pkg"
+ "errors"
+ "fmt"
+ "github.com/gofiber/fiber/v2"
+ "github.com/spf13/viper"
+)
+
+type ErrorPayload struct {
+ Title string `json:"title"`
+ Message string `json:"message"`
+ Version string `json:"version"`
+}
+
+func StatusPageHandler(c *fiber.Ctx, err error) error {
+ var reqErr *fiber.Error
+ var status = fiber.StatusInternalServerError
+ if errors.As(err, &reqErr) {
+ status = reqErr.Code
+ }
+
+ c.Status(status)
+
+ payload := ErrorPayload{
+ Version: roadsign.AppVersion,
+ }
+
+ switch status {
+ case fiber.StatusNotFound:
+ payload.Title = "Not Found"
+ payload.Message = fmt.Sprintf("no resource for \"%s\"", c.OriginalURL())
+ return c.Render("views/not-found", payload)
+ case fiber.StatusTooManyRequests:
+ payload.Title = "Request Too Fast"
+ payload.Message = fmt.Sprintf("you have sent over %d request(s) in a second", viper.GetInt("hypertext.limitation.max_qps"))
+ return c.Render("views/too-many-requests", payload)
+ case fiber.StatusRequestEntityTooLarge:
+ payload.Title = "Request Too Large"
+ payload.Message = fmt.Sprintf("you have sent a request over %d bytes", viper.GetInt("hypertext.limitation.max_body_size"))
+ return c.Render("views/request-too-large", payload)
+ case fiber.StatusBadGateway:
+ payload.Title = "Backend Down"
+ payload.Message = fmt.Sprintf("all destnations configured to handle your request are down: %s", err.Error())
+ return c.Render("views/bad-gateway", payload)
+ case fiber.StatusGatewayTimeout:
+ payload.Title = "Backend Took Too Long To Response"
+ payload.Message = fmt.Sprintf("the destnation took too long to response your request: %s", err.Error())
+ return c.Render("views/gateway-timeout", payload)
+ default:
+ payload.Title = "Oops"
+ payload.Message = err.Error()
+ return c.Render("views/fallback", payload)
+ }
+}
diff --git a/pkg/hypertext/status/views/bad-gateway.gohtml b/pkg/hypertext/status/views/bad-gateway.gohtml
new file mode 100644
index 0000000..b12685b
--- /dev/null
+++ b/pkg/hypertext/status/views/bad-gateway.gohtml
@@ -0,0 +1,6 @@
+
502
+
No one is standing...
+
+
+ {{ .Message }}
+
\ No newline at end of file
diff --git a/pkg/hypertext/status/views/fallback.gohtml b/pkg/hypertext/status/views/fallback.gohtml
new file mode 100644
index 0000000..ab0ce6d
--- /dev/null
+++ b/pkg/hypertext/status/views/fallback.gohtml
@@ -0,0 +1,6 @@
+
Oops
+
Something went wrong...
+
+
+ {{ .Message }}
+
\ No newline at end of file
diff --git a/pkg/hypertext/status/views/gateway-timeout.gohtml b/pkg/hypertext/status/views/gateway-timeout.gohtml
new file mode 100644
index 0000000..cf5c4ef
--- /dev/null
+++ b/pkg/hypertext/status/views/gateway-timeout.gohtml
@@ -0,0 +1,6 @@
+
504
+
Looks like the server in the back fell asleep
+
+
+ {{ .Message }}
+
\ No newline at end of file
diff --git a/pkg/hypertext/status/views/index.gohtml b/pkg/hypertext/status/views/index.gohtml
new file mode 100644
index 0000000..ce4a402
--- /dev/null
+++ b/pkg/hypertext/status/views/index.gohtml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ .Title }} | RoadSign
+
+
+
+
+ {{embed}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pkg/hypertext/status/views/not-found.gohtml b/pkg/hypertext/status/views/not-found.gohtml
new file mode 100644
index 0000000..817e950
--- /dev/null
+++ b/pkg/hypertext/status/views/not-found.gohtml
@@ -0,0 +1,6 @@
+
404
+
Not Found
+
+
+ {{ .Message }}
+
\ No newline at end of file
diff --git a/pkg/hypertext/status/views/request-too-large.gohtml b/pkg/hypertext/status/views/request-too-large.gohtml
new file mode 100644
index 0000000..f2c5e07
--- /dev/null
+++ b/pkg/hypertext/status/views/request-too-large.gohtml
@@ -0,0 +1,6 @@
+
413
+
Auh, you are too big.
+
+
+ {{ .Message }}
+
\ No newline at end of file
diff --git a/pkg/hypertext/status/views/too-many-requests.gohtml b/pkg/hypertext/status/views/too-many-requests.gohtml
new file mode 100644
index 0000000..269d1d9
--- /dev/null
+++ b/pkg/hypertext/status/views/too-many-requests.gohtml
@@ -0,0 +1,6 @@
+