handler.go
1package routing
2
3import (
4 "fmt"
5 "net/http"
6 "os"
7 "path/filepath"
8 "strings"
9)
10
11// RouteFileHandler serves route files from the data directory.
12// URL pattern: /route/{hash}.{polyline6,geojson}
13// Maps to: ./data/route/{hash[0:2]}/{hash[2:]}.{ext}
14func RouteFileHandler(baseDir string) http.HandlerFunc {
15 return func(w http.ResponseWriter, r *http.Request) {
16 // Extract hash and extension from URL path
17 // Path is expected to be /route/{hash}.{ext}
18 path := strings.TrimPrefix(r.URL.Path, "/route/")
19 if path == "" {
20 http.Error(w, "Not found", http.StatusNotFound)
21 return
22 }
23
24 // Split into hash and extension
25 parts := strings.SplitN(path, ".", 2)
26 if len(parts) != 2 {
27 http.Error(w, "Invalid filename format", http.StatusBadRequest)
28 return
29 }
30
31 hash := parts[0]
32 ext := parts[1]
33
34 // Validate hash length (SHA256 hex = 64 chars)
35 if len(hash) != 64 {
36 http.Error(w, "Invalid hash", http.StatusBadRequest)
37 return
38 }
39
40 // Validate extension
41 var contentType string
42 switch ext {
43 case "polyline6":
44 contentType = "text/plain"
45 case "geojson":
46 contentType = "application/geo+json"
47 default:
48 http.Error(w, "Unsupported file type", http.StatusBadRequest)
49 return
50 }
51
52 // Construct file path: {baseDir}/{hash[0:2]}/{hash[2:]}.{ext}
53 filePath := filepath.Join(baseDir, hash[0:2], hash[2:]+"."+ext)
54
55 // Open the file
56 file, err := os.Open(filePath)
57 if err != nil {
58 if os.IsNotExist(err) {
59 http.Error(w, "Not found", http.StatusNotFound)
60 return
61 }
62 http.Error(w, fmt.Sprintf("Error opening file: %v", err), http.StatusInternalServerError)
63 return
64 }
65 defer file.Close()
66
67 // Get file info for Content-Length
68 stat, err := file.Stat()
69 if err != nil {
70 http.Error(w, fmt.Sprintf("Error getting file info: %v", err), http.StatusInternalServerError)
71 return
72 }
73
74 // Set headers
75 w.Header().Set("Content-Type", contentType)
76 w.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size()))
77 w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
78
79 // Stream the file
80 w.WriteHeader(http.StatusOK)
81 http.ServeContent(w, r, filePath, stat.ModTime(), file)
82 }
83}