just serve static files.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

99 lines
3.6 KiB

package web
import (
"bytes"
"fmt"
"github.com/valyala/fasthttp"
"net"
"os"
"strings"
"strconv"
)
func Run(middlewares... func(ctx *fasthttp.RequestCtx) bool) error {
fmt.Println("Listening on http://" + envOr("HOST", "[::]") + ":" + envOr("PORT", "80"))
enableHidden := envFlag("ENABLE_HIDDEN", false)
enableCache := envFlag("ENABLE_CACHE", true)
enableCors := envFlag("ENABLE_CORS", false)
notFound := "/" + strings.TrimPrefix(envOr("NOT_FOUND", "404.html"), "/")
notFoundStatus, _ := strconv.Atoi(envOr("NOT_FOUND_STATUS", "404"))
fsHandler := (&fasthttp.FS{
Root: ".",
GenerateIndexPages: envFlag("ENABLE_INDEX", false),
Compress: os.Getenv("ENABLE_COMPRESSION") == "cached",
CompressBrotli: true,
AcceptByteRange: envFlag("ENABLE_RANGE", true),
IndexNames: strings.Split(envOr("INDEX_FILES", "index.html;index.htm"), ";"),
PathNotFound: func(ctx *fasthttp.RequestCtx) {
ctx.Response.SetStatusCode(404)
},
}).NewRequestHandler()
if os.Getenv("ENABLE_COMPRESSION") != "cached" && envFlag("ENABLE_COMPRESSION", true) {
fsHandler = fasthttp.CompressHandlerBrotliLevel(fsHandler, fasthttp.CompressBrotliBestSpeed, fasthttp.CompressBestSpeed)
}
listener, err := net.Listen("tcp", envOr("HOST", "[::]") + ":" + envOr("PORT", "80"))
if err != nil {
return err
}
return (&fasthttp.Server{
Logger: enabledLogger(envFlag("ENABLE_DEBUG", false)),
Handler: func(ctx *fasthttp.RequestCtx) {
for _, middleware := range middlewares {
if !middleware(ctx) {
return
}
}
if !ctx.IsGet() && !ctx.IsHead() && !ctx.IsOptions() { // block non-static methods
ctx.Error("Method not allowed", 405)
} else if !enableHidden && bytes.Contains(ctx.Path(), []byte{'/', '.'}) { // block hidden files
ctx.Logger().Printf("dotfile access is disabled")
ctx.Response.SetStatusCode(404)
} else if !ctx.IsOptions() { // let fasthttp handle the request
fsHandler(ctx)
}
if ctx.Response.StatusCode() > 399 { // serve not found page for all request/server errors
if notFound != "/" { // try NOT_FOUND first if set
ctx.Request.Reset()
ctx.Response.Reset()
ctx.Request.SetRequestURI(notFound)
fsHandler(ctx) // we can't use ServeFile as it doesn't respect compression/indexing options
}
if ctx.Response.StatusCode() > 399 { // fall back to a plain text response if that failed
ctx.Error("File not found", notFoundStatus)
}
ctx.SetStatusCode(notFoundStatus)
}
ctx.Response.Header.Set("Server", "codeberg.org/momar/web")
ctx.Response.Header.Set("Referrer-Policy", "strict-origin-when-cross-origin")
if enableCors && ctx.Response.StatusCode() == 200 {
ctx.Response.Header.Set("Access-Control-Allow-Origin", "*")
ctx.Response.Header.Set("Access-Control-Allow-Methods", "GET, HEAD")
ctx.Response.Header.Set("Access-Control-Allow-Headers", "Range")
}
ctx.Response.Header.Set("Cache-Control", "must-revalidate")
if !enableCache || ctx.Response.StatusCode() >= 300 {
ctx.Response.Header.Set("Cache-Control", "no-cache")
}
},
}).Serve(listener)
}
type enabledLogger bool // debug logger that can be disabled by setting it to false
func (l enabledLogger) Printf(format string, args ...interface{}) {
if l {
fmt.Printf(format+"\n", args...)
}
}
func envOr(env string, or string) string { // helpers for environment variables
if v := os.Getenv(env); v != "" {
return v
}
return or
}
func envFlag(env string, or bool) bool {
if !or {
return strings.ContainsAny(os.Getenv(env), "YyTt1") // default: false, only return true if intended
}
return !strings.ContainsAny(os.Getenv(env), "NnFf0") // default: true, only return false if intended
}