staticrouter
This commit is contained in:
parent
440f0a8378
commit
6e3fe5e9ef
88
core/routers/staticrouter.go
Normal file
88
core/routers/staticrouter.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StaticRouter is a filesystem-based router for static files
|
||||||
|
type StaticRouter struct {
|
||||||
|
rootDir string // Root directory containing files
|
||||||
|
routes map[string]string // Direct mapping from URL path to file path
|
||||||
|
mu sync.RWMutex // Lock for concurrent access to routes
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStaticRouter creates a new StaticRouter instance
|
||||||
|
func NewStaticRouter(rootDir string) (*StaticRouter, error) {
|
||||||
|
// Verify root directory exists
|
||||||
|
info, err := os.Stat(rootDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
return nil, errors.New("root path is not a directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &StaticRouter{
|
||||||
|
rootDir: rootDir,
|
||||||
|
routes: make(map[string]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build routes
|
||||||
|
if err := r.buildRoutes(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildRoutes scans the root directory and builds the routing map
|
||||||
|
func (r *StaticRouter) buildRoutes() error {
|
||||||
|
return filepath.Walk(r.rootDir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip directories
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get relative path for URL
|
||||||
|
relPath, err := filepath.Rel(r.rootDir, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to URL path with forward slashes for consistency
|
||||||
|
urlPath := "/" + strings.ReplaceAll(relPath, "\\", "/")
|
||||||
|
|
||||||
|
// Add to routes map
|
||||||
|
r.routes[urlPath] = path
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match finds a file path for the given URL path
|
||||||
|
func (r *StaticRouter) Match(path string) (string, bool) {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
|
filePath, found := r.routes[path]
|
||||||
|
return filePath, found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh rebuilds the router by rescanning the root directory
|
||||||
|
func (r *StaticRouter) Refresh() error {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
// Clear routes
|
||||||
|
r.routes = make(map[string]string)
|
||||||
|
|
||||||
|
// Rebuild routes
|
||||||
|
return r.buildRoutes()
|
||||||
|
}
|
150
core/routers/staticrouter_test.go
Normal file
150
core/routers/staticrouter_test.go
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupStaticFiles(t *testing.T) (string, func()) {
|
||||||
|
// Create a temporary directory
|
||||||
|
tempDir, err := os.MkdirTemp("", "staticrouter-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temp directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create file structure
|
||||||
|
files := map[string]string{
|
||||||
|
"index.html": "<html>Home</html>",
|
||||||
|
"about.html": "<html>About</html>",
|
||||||
|
"api/index.json": `{"version": "1.0"}`,
|
||||||
|
"users/index.html": "<html>Users</html>",
|
||||||
|
"users/123/profile.html": "<html>User Profile</html>",
|
||||||
|
"posts/hello-world/comments.html": "<html>Post Comments</html>",
|
||||||
|
"docs/v1/api.html": "<html>API Docs</html>",
|
||||||
|
}
|
||||||
|
|
||||||
|
for path, content := range files {
|
||||||
|
filePath := filepath.Join(tempDir, path)
|
||||||
|
|
||||||
|
// Create directories
|
||||||
|
err := os.MkdirAll(filepath.Dir(filePath), 0755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create directory %s: %v", filepath.Dir(filePath), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create file
|
||||||
|
err = os.WriteFile(filePath, []byte(content), 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create file %s: %v", filePath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return cleanup function
|
||||||
|
cleanup := func() {
|
||||||
|
os.RemoveAll(tempDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tempDir, cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStaticRouterInitialization(t *testing.T) {
|
||||||
|
rootDir, cleanup := setupStaticFiles(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
router, err := NewStaticRouter(rootDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create router: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if router == nil {
|
||||||
|
t.Fatal("Router is nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStaticRouteMatching(t *testing.T) {
|
||||||
|
rootDir, cleanup := setupStaticFiles(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
router, err := NewStaticRouter(rootDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create router: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
path string
|
||||||
|
wantFound bool
|
||||||
|
wantHandler string
|
||||||
|
}{
|
||||||
|
{"/index.html", true, filepath.Join(rootDir, "index.html")},
|
||||||
|
{"/about.html", true, filepath.Join(rootDir, "about.html")},
|
||||||
|
{"/api/index.json", true, filepath.Join(rootDir, "api/index.json")},
|
||||||
|
{"/users/index.html", true, filepath.Join(rootDir, "users/index.html")},
|
||||||
|
{"/users/123/profile.html", true, filepath.Join(rootDir, "users/123/profile.html")},
|
||||||
|
{"/posts/hello-world/comments.html", true, filepath.Join(rootDir, "posts/hello-world/comments.html")},
|
||||||
|
{"/docs/v1/api.html", true, filepath.Join(rootDir, "docs/v1/api.html")},
|
||||||
|
|
||||||
|
// Non-existent routes
|
||||||
|
{"/nonexistent.html", false, ""},
|
||||||
|
{"/api/nonexistent.json", false, ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.path, func(t *testing.T) {
|
||||||
|
filePath, found := router.Match(tt.path)
|
||||||
|
|
||||||
|
if found != tt.wantFound {
|
||||||
|
t.Errorf("Match() found = %v, want %v", found, tt.wantFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if filePath != tt.wantHandler {
|
||||||
|
t.Errorf("Match() handler = %v, want %v", filePath, tt.wantHandler)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TestStaticParamExtraction has been removed since we no longer extract parameters
|
||||||
|
|
||||||
|
func TestStaticRefresh(t *testing.T) {
|
||||||
|
rootDir, cleanup := setupStaticFiles(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
router, err := NewStaticRouter(rootDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create router: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new file
|
||||||
|
newFilePath := filepath.Join(rootDir, "new.html")
|
||||||
|
err = os.WriteFile(newFilePath, []byte("<html>New</html>"), 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before refresh, file should not be found
|
||||||
|
_, found := router.Match("/new.html")
|
||||||
|
if found {
|
||||||
|
t.Errorf("New file should not be found before refresh")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh router
|
||||||
|
err = router.Refresh()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to refresh router: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// After refresh, file should be found
|
||||||
|
filePath, found := router.Match("/new.html")
|
||||||
|
if !found {
|
||||||
|
t.Errorf("New file should be found after refresh")
|
||||||
|
}
|
||||||
|
|
||||||
|
if filePath != newFilePath {
|
||||||
|
t.Errorf("Expected path %s, got %s", newFilePath, filePath)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user