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