diff --git a/README.md b/README.md index a79a349..f952be6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ -# FAST - HTTP Static Site Server +# FAST - HTTP Static Site Server and Reverse Proxy -FAST (File Access Speedy Transfer) is a lightweight, high-performance web server designed specifically for serving static content. It prioritizes speed and efficiency, making it ideal for quickly delivering HTML, CSS, JavaScript, images, and other static assets to web browsers. +FAST (File Access Speedy Transfer) is a lightweight, +high-performance web server designed for serving static content and acting as a reverse proxy. +It prioritizes speed and efficiency, making it ideal for quickly delivering static assets and proxying requests to +backend services. ## Features @@ -8,10 +11,11 @@ FAST (File Access Speedy Transfer) is a lightweight, high-performance web server - SSL/TLS encryption - HTTP to HTTPS redirection - Domain-specific public directories +- Reverse proxy support - Easy configuration via YAML - Systemd service integration - Minimal configuration required, allowing for quick setup and deployment -- Optimized for serving static files, without the overhead of dynamic content processing +- Optimized for serving static files and proxying requests - High concurrency, able to handle multiple simultaneous connections efficiently - Low memory footprint, making it suitable for various hosting environments - Built-in caching mechanisms to further enhance performance @@ -27,7 +31,7 @@ FAST (File Access Speedy Transfer) is a lightweight, high-performance web server ## Installation 1. Clone the repository: - + ```bash git clone https://github.com/pollystack/fast.git cd fast-server @@ -39,8 +43,8 @@ FAST (File Access Speedy Transfer) is a lightweight, high-performance web server ```bash sudo make install ``` - - This will: + + This will: - Build the server for Linux - Copy the binary to `/usr/local/bin/fast_server` - Copy the configuration to `/etc/fast/config.yaml` @@ -58,34 +62,40 @@ The server is configured via the `config.yaml` file. Here's an example configura ```yaml server: - port: 443 - http_port: 80 # for HTTP to HTTPS redirect + port: 443 + http_port: 80 # for HTTP to HTTPS redirect domains: - - name: example.com - public_dir: /var/www/fast/example.com - ssl: - cert_file: /etc/fast/ssl/example.com/fullchain.pem - key_file: /etc/fast/ssl/example.com/privkey.pem - - - name: another-domain.com - public_dir: /var/www/fast/another-domain.com - ssl: - cert_file: /etc/fast/ssl/another-domain.com/fullchain.pem - key_file: /etc/fast/ssl/another-domain.com/privkey.pem + - name: example.com + type: static + public_dir: /var/www/fast/example.com + ssl: + cert_file: /etc/fast/ssl/example.com/fullchain.pem + key_file: /etc/fast/ssl/example.com/privkey.pem + + - name: api.example.com + type: proxy + proxy: + host: 192.168.1.100 + port: 8000 + ssl: + cert_file: /etc/fast/ssl/api.example.com/fullchain.pem + key_file: /etc/fast/ssl/api.example.com/privkey.pem global_ssl: - cert_file: /etc/fast/ssl/global/fullchain.pem - key_file: /etc/fast/ssl/global/privkey.pem + cert_file: /etc/fast/ssl/global/fullchain.pem + key_file: /etc/fast/ssl/global/privkey.pem log: - file: /var/log/fast/server.log - level: info # Options: debug, info, warn, error + file: /var/log/fast/server.log + level: info # Options: debug, info, warn, error settings: - read_timeout: 5s - write_timeout: 10s - graceful_shutdown_timeout: 30s + read_timeout: 5s + write_timeout: 10s + graceful_shutdown_timeout: 30s + +is_development: false ``` @@ -164,20 +174,20 @@ go install github.com/google/gops@latest ```azure fast-server/ -├── main.go -├── config/ -│ └── config.go -├── server/ -│ └── server.go -├── handlers/ -│ └── handlers.go -├── public/ -│ └── ... (static files) -├── Makefile -├── config.yaml.exmple -└── README.md -└── Dockerfile -└── LICENSE + ├── main.go + ├── config/ + │ └── config.go + ├── server/ + │ └── server.go + ├── handlers/ + │ └── handlers.go + ├── public/ + │ └── ... (static files) + ├── Makefile + ├── config.yaml.exmple + └── README.md + └── Dockerfile + └── LICENSE ``` ### Building diff --git a/fast-server/config/config.go b/fast-server/config/config.go index c1ae03f..457c20f 100644 --- a/fast-server/config/config.go +++ b/fast-server/config/config.go @@ -1,98 +1,103 @@ package config import ( - "fmt" - "gopkg.in/yaml.v2" - "io/ioutil" - "log" - "os" - "os/exec" - "runtime" - "strconv" - "strings" + "fmt" + "gopkg.in/yaml.v2" + "io/ioutil" + "log" + "os" + "os/exec" + "runtime" + "strconv" + "strings" ) var ProductionConfigPath = "/etc/fast/config.yaml" type Domain struct { - Name string `yaml:"name"` - PublicDir string `yaml:"public_dir"` - SSL struct { - CertFile string `yaml:"cert_file"` - KeyFile string `yaml:"key_file"` - } `yaml:"ssl"` + Name string `yaml:"name"` + Type string `yaml:"type"` + PublicDir string `yaml:"public_dir"` + Proxy struct { + Host string `yaml:"host"` + Port int `yaml:"port"` + } `yaml:"proxy"` + SSL struct { + CertFile string `yaml:"cert_file"` + KeyFile string `yaml:"key_file"` + } `yaml:"ssl"` } type Config struct { - Server struct { - Port int `yaml:"port"` - HTTPPort int `yaml:"http_port"` - } `yaml:"server"` - Domains []Domain `yaml:"domains"` - GlobalSSL struct { - CertFile string `yaml:"cert_file"` - KeyFile string `yaml:"key_file"` - } `yaml:"global_ssl"` - Log struct { - File string `yaml:"file"` - Level string `yaml:"level"` - } `yaml:"log"` - Settings struct { - ReadTimeout string `yaml:"read_timeout"` - WriteTimeout string `yaml:"write_timeout"` - GracefulShutdownTimeout string `yaml:"graceful_shutdown_timeout"` - } `yaml:"settings"` - IsDevelopment bool `yaml:"is_development"` + Server struct { + Port int `yaml:"port"` + HTTPPort int `yaml:"http_port"` + } `yaml:"server"` + Domains []Domain `yaml:"domains"` + GlobalSSL struct { + CertFile string `yaml:"cert_file"` + KeyFile string `yaml:"key_file"` + } `yaml:"global_ssl"` + Log struct { + File string `yaml:"file"` + Level string `yaml:"level"` + } `yaml:"log"` + Settings struct { + ReadTimeout string `yaml:"read_timeout"` + WriteTimeout string `yaml:"write_timeout"` + GracefulShutdownTimeout string `yaml:"graceful_shutdown_timeout"` + } `yaml:"settings"` + IsDevelopment bool `yaml:"is_development"` } func isLaunchedByDebugger() bool { - // Check if gops is available - _, err := exec.LookPath("gops") - if err != nil { - // If gops is not available, fall back to a simple check - return strings.Contains(os.Args[0], "debugger") || strings.Contains(os.Args[0], "___go_build_") - } + // Check if gops is available + _, err := exec.LookPath("gops") + if err != nil { + // If gops is not available, fall back to a simple check + return strings.Contains(os.Args[0], "debugger") || strings.Contains(os.Args[0], "___go_build_") + } - // Use gops to check the parent process - gopsOut, err := exec.Command("gops", strconv.Itoa(os.Getppid())).Output() - if err != nil { - log.Printf("Error running gops: %v", err) - return false - } + // Use gops to check the parent process + gopsOut, err := exec.Command("gops", strconv.Itoa(os.Getppid())).Output() + if err != nil { + log.Printf("Error running gops: %v", err) + return false + } - gopsOutStr := string(gopsOut) + gopsOutStr := string(gopsOut) - switch runtime.GOOS { - case "windows": - return strings.Contains(gopsOutStr, "\\dlv.exe") - case "darwin": - return strings.Contains(gopsOutStr, "/dlv") || - strings.Contains(gopsOutStr, "/dlv-dap") || - strings.Contains(gopsOutStr, "debugserver") - default: // linux and others - return strings.Contains(gopsOutStr, "/dlv") - } + switch runtime.GOOS { + case "windows": + return strings.Contains(gopsOutStr, "\\dlv.exe") + case "darwin": + return strings.Contains(gopsOutStr, "/dlv") || + strings.Contains(gopsOutStr, "/dlv-dap") || + strings.Contains(gopsOutStr, "debugserver") + default: // linux and others + return strings.Contains(gopsOutStr, "/dlv") + } } func LoadConfig() (*Config, error) { - var configPath string - if isLaunchedByDebugger() { - configPath = "fast-server/test/config.yaml" // Local path for development - log.Println("Debug mode detected. Using local config.yaml") - } else { - configPath = ProductionConfigPath // Default production path - } + var configPath string + if isLaunchedByDebugger() { + configPath = "test/config.yaml" // Local path for development + log.Println("Debug mode detected. Using local config.yaml") + } else { + configPath = ProductionConfigPath // Default production path + } - data, err := ioutil.ReadFile(configPath) - if err != nil { - return nil, fmt.Errorf("failed to read config file: %v", err) - } + data, err := ioutil.ReadFile(configPath) + if err != nil { + return nil, fmt.Errorf("failed to read config file: %v", err) + } - var config Config - err = yaml.Unmarshal(data, &config) - if err != nil { - return nil, fmt.Errorf("failed to parse config file: %v", err) - } + var config Config + err = yaml.Unmarshal(data, &config) + if err != nil { + return nil, fmt.Errorf("failed to parse config file: %v", err) + } - return &config, nil + return &config, nil } diff --git a/fast-server/handlers/handlers.go b/fast-server/handlers/handlers.go index 5e4209f..f445f38 100644 --- a/fast-server/handlers/handlers.go +++ b/fast-server/handlers/handlers.go @@ -1,81 +1,113 @@ package handlers import ( - "fast/config" - "github.com/labstack/echo/v4" - "log" - "os" - "path/filepath" - "strings" + "fast/config" + "fmt" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "log" + "net/url" + "os" + "path/filepath" + "strings" ) func SetupDomainRoutes(e *echo.Echo, domains []config.Domain) { - // Create a map for quick domain lookup - domainMap := make(map[string]config.Domain) - for _, domain := range domains { - domainMap[domain.Name] = domain - } + // Create a map for quick domain lookup + domainMap := make(map[string]config.Domain) + for _, domain := range domains { + domainMap[domain.Name] = domain + } - // Single middleware to handle all domains - e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - host := c.Request().Host - if strings.Contains(host, ":") { - host = strings.Split(host, ":")[0] - } - log.Printf("Incoming request for host: %s", host) + // Single middleware to handle all domains + e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + host := c.Request().Host + if strings.Contains(host, ":") { + host = strings.Split(host, ":")[0] + } + log.Printf("Incoming request for host: %s", host) - var matchedDomain config.Domain - var matchedName string - for domainName, domain := range domainMap { - if strings.HasSuffix(host, domainName) { - if len(domainName) > len(matchedName) { - matchedDomain = domain - matchedName = domainName - } - } - } + var matchedDomain config.Domain + var matchedName string + for domainName, domain := range domainMap { + if strings.HasSuffix(host, domainName) { + if len(domainName) > len(matchedName) { + matchedDomain = domain + matchedName = domainName + } + } + } - if matchedName == "" { - log.Printf("No matching domain found for host: %s", host) - return echo.ErrNotFound - } + if matchedName == "" { + log.Printf("No matching domain found for host: %s", host) + return echo.ErrNotFound + } - log.Printf("Matched domain: %s", matchedName) - c.Set("domain", matchedDomain) - return next(c) - } - }) + log.Printf("Matched domain: %s", matchedName) + c.Set("domain", matchedDomain) + return next(c) + } + }) - // Root handler - e.GET("/", func(c echo.Context) error { - domain := c.Get("domain").(config.Domain) - return serveIndexOrFile(c, domain.PublicDir, "index.html") - }) + // Root handler + e.GET("/", func(c echo.Context) error { + domain := c.Get("domain").(config.Domain) + if domain.Type == "proxy" { + return handleProxy(c, domain) + } + return serveIndexOrFile(c, domain.PublicDir, "index.html") + }) - // Static file handler - e.GET("/*", func(c echo.Context) error { - domain := c.Get("domain").(config.Domain) - return serveIndexOrFile(c, domain.PublicDir, c.Request().URL.Path) - }) + // Static file handler or proxy handler + e.Any("/*", func(c echo.Context) error { + domain := c.Get("domain").(config.Domain) + if domain.Type == "proxy" { + return handleProxy(c, domain) + } + return serveIndexOrFile(c, domain.PublicDir, c.Request().URL.Path) + }) +} + +func handleProxy(c echo.Context, domain config.Domain) error { + target, err := url.Parse(fmt.Sprintf("http://%s:%d", domain.Proxy.Host, domain.Proxy.Port)) + if err != nil { + log.Printf("Error parsing proxy URL: %v", err) + return echo.ErrInternalServerError + } + + proxyMiddleware := middleware.ProxyWithConfig(middleware.ProxyConfig{ + Balancer: middleware.NewRoundRobinBalancer([]*middleware.ProxyTarget{ + { + URL: target, + }, + }), + Rewrite: map[string]string{ + "/*": "/$1", + }, + }) + + return proxyMiddleware(func(c echo.Context) error { + return nil + })(c) } func serveIndexOrFile(c echo.Context, publicDir, requestPath string) error { - fullPath := filepath.Join(publicDir, filepath.Clean(requestPath)) + fullPath := filepath.Join(publicDir, filepath.Clean(requestPath)) - // Prevent directory traversal - if !strings.HasPrefix(fullPath, publicDir) { - log.Printf("Attempted directory traversal detected: %s", fullPath) - return echo.ErrNotFound - } + // Prevent directory traversal + if !strings.HasPrefix(fullPath, publicDir) { + log.Printf("Attempted directory traversal detected: %s", fullPath) + return echo.ErrNotFound + } - if stat, err := os.Stat(fullPath); err == nil && !stat.IsDir() { - log.Printf("Serving file: %s", fullPath) - return c.File(fullPath) - } + if stat, err := os.Stat(fullPath); err == nil && !stat.IsDir() { + log.Printf("Serving file: %s", fullPath) + return c.File(fullPath) + } - // If file doesn't exist or is a directory, serve the root index.html - indexPath := filepath.Join(publicDir, "index.html") - log.Printf("Serving index.html: %s", indexPath) - return c.File(indexPath) + // If file doesn't exist or is a directory, serve the root index.html + indexPath := filepath.Join(publicDir, "index.html") + log.Printf("Serving index.html: %s", indexPath) + return c.File(indexPath) } diff --git a/fast-server/test/config.yaml b/fast-server/test/config.yaml index 6098969..ed6fe9c 100644 --- a/fast-server/test/config.yaml +++ b/fast-server/test/config.yaml @@ -4,20 +4,31 @@ server: domains: - name: domain1.lan - public_dir: fast-server/test/public/domain1.lan + type: static + public_dir: test/public/domain1.lan ssl: - cert_file: fast-server/test/ssl/domain1.lan/fullchain.pem - key_file: fast-server/test/ssl/domain1.lan/privkey.pem + cert_file: test/ssl/domain1.lan/fullchain.pem + key_file: test/ssl/domain1.lan/privkey.pem - name: domain2.lan - public_dir: fast-server/test/public/domain2.lan + type: static + public_dir: test/public/domain2.lan ssl: - cert_file: fast-server/test/ssl/domain2.lan/fullchain.pem - key_file: fast-server/test/ssl/domain2.lan/privkey.pem + cert_file: test/ssl/domain2.lan/fullchain.pem + key_file: test/ssl/domain2.lan/privkey.pem + + - name: domain3.lan + type: proxy + proxy: + host: 127.0.0.1 + port: 8000 + ssl: + cert_file: test/ssl/domain2.lan/fullchain.pem + key_file: test/ssl/domain2.lan/privkey.pem global_ssl: - cert_file: fast-server/test/ssl/global/fullchain.pem - key_file: fast-server/test/ssl/global/privkey.pem + cert_file: test/ssl/global/fullchain.pem + key_file: test/ssl/global/privkey.pem log: file: log/server.log @@ -28,4 +39,4 @@ settings: write_timeout: 10s graceful_shutdown_timeout: 30s -is_development: true +is_development: true \ No newline at end of file diff --git a/fast-server/test/ssl/domain3.lan/fullchain.pem b/fast-server/test/ssl/domain3.lan/fullchain.pem new file mode 100644 index 0000000..eb0ac7f --- /dev/null +++ b/fast-server/test/ssl/domain3.lan/fullchain.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFDTCCAvWgAwIBAgIUX3Nbm3mIl2k5shgZcNyLdFCTueEwDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLZG9tYWluMy5sYW4wHhcNMjQwOTEyMjE1NjU2WhcNMjUw +OTEyMjE1NjU2WjAWMRQwEgYDVQQDDAtkb21haW4zLmxhbjCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBANAO7xDy6eqzyqxh8UA1wqiiUZBu4PC8NpIKFgNs +Qp58I4FNMWR8SzlpIVU2yb8+fu0FboYMkvhEj+S1XdLa5AVv1vdtVSF7hiaoA3Hz +j0LrxHugltdmwKUxwnK5qiXMgSU7Fu/u+yqUgHT+LMC/wGPVGHvW6nbAW460uvEi +lcK2lvwF3ZrQfhteDnjkotSKXHqxZXIsbuHdMJqRHbtMzo6XmfhtrVLH+39nwdN7 +9cP2OyoCT4KVn09w1/uXOUvXcD1kThMlhGz7Un9n/KWKWVsWZL/aFXTMYm/GT5eV +rSb23em9FKPxsJnkqKC9wU2lvbkK4NEZB5CbyFp2R9/E8JEj3C/WOLfvqi7S3a9O +PM08rzBMR2/o6+E83jN1YQlZYDGz0eCuFO7a7ZRJ+1vwoQW2AOy3OGLbLHZMSUJd +PQ1w/5kelFGMYrecfpTw3pQnUPmtaojebNE6J8yQC0ssplaeWOpUJ6s+dm9zLHx4 +tlR2JxtisUQ/LZYcHggFjSqKL1BxqGeGMwDI/SEVh8hrlYcshBTxele7yY5kA3lr +yuIdrt5PVo0e2YGe0xwYRAVcqBCK3UIf/LPddy06mXOuvt4cChUqu+gMof+Gy5fy +Iv24jGQVA6NHHKSa3eDxrwlsBU43WGRUMtCOBP/XbTSMyb/Ggh/PCNDktaf0Zbc0 +6oyrAgMBAAGjUzBRMB0GA1UdDgQWBBSm33SzMstqMKVo6a028Xg4KbCKVDAfBgNV +HSMEGDAWgBSm33SzMstqMKVo6a028Xg4KbCKVDAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4ICAQBM+z2rlxY62BtkrxggWtlOkvq/VvOl8UZatDkFbpXQ +saGgg3Pyu9ymHO6yRs199cst9jj8RTNbVA/hPNljgFTATuIDLsPdWLII+ISc9bB+ +EeQFL5m3Oes3HwQn9C+AZUo+v31XSapJZKRbLhHo93I27hNBeLJS4Vng1SP2jNFJ +tftS1V3Kuh156lFppXUwY7b2mvxIAOgw7IU/Li1lwg6eqK/cw1aj5HOo3CWirn4/ +iQn81k37yYx2kieCrTsw3YiStt0BhVQNclCKqAySZYMxCAzUf/wpf+Eb2Qx2e358 +RZIvKzxqtc5wkBisuPMv5TLJdBKyRNGqygFoOOk+wlIRdwTVYlzitobX0XYO+ljf +4Muoza0T6miagLHrF1FTl2aoVPB6WOft8HpBKs7GY+iF+cVk3E7GQ9qcT54JKijR +VS/BHeG/K9T0qsUqvlqGpuMtYMfcavuCo3ShjCz1Kye2E77nM5Ux2WBD+/SDX7gt +iS8ZYIye+xmsf2uFt7gTwgvwPzXzXgT9XrG1/8HBdbz/2AYvqKLPf2a30GvEksFW +NApbhTaGiNgg4RXrwSOZbUs/hm8TbX2vrNdv3VAoHC0Pl5s0JgCU7DOzT+z1MG6O +TacPQl6/HmpnvEST3Pvd2TXqJFH9jPi7ufbPgrhjcbeNSezkJjLUIrqd0lEnfCBN +LA== +-----END CERTIFICATE----- diff --git a/fast-server/test/ssl/domain3.lan/privkey.pem b/fast-server/test/ssl/domain3.lan/privkey.pem new file mode 100644 index 0000000..c9f53fe --- /dev/null +++ b/fast-server/test/ssl/domain3.lan/privkey.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +iZH/33DX52bhWof/TkNqXiEtz+g4 +-----END PRIVATE KEY-----