diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 9075ce6..2d54ead 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -20,6 +20,11 @@ import ( "github.com/go-chi/chi/v5" ) +const ( + // statsContextTimeout is the timeout for stats API operations + statsContextTimeout = 4 * time.Second +) + // handleRoot returns a handler that redirects to /status func (s *Server) handleRoot() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -81,7 +86,7 @@ func (s *Server) handleStatusJSON() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Create a 4 second timeout context for this request - ctx, cancel := context.WithTimeout(r.Context(), 4*time.Second) + ctx, cancel := context.WithTimeout(r.Context(), statsContextTimeout) defer cancel() metrics := s.streamer.GetMetrics() @@ -215,7 +220,7 @@ func (s *Server) handleStats() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Create a 4 second timeout context for this request - ctx, cancel := context.WithTimeout(r.Context(), 4*time.Second) + ctx, cancel := context.WithTimeout(r.Context(), statsContextTimeout) defer cancel() // Check if context is already cancelled @@ -762,7 +767,7 @@ func (s *Server) handleIPRedirect() http.HandlerFunc { } } -// handlePrefixLength shows a random sample of prefixes with the specified mask length +// handlePrefixLength shows a random sample of IPv4 prefixes with the specified mask length func (s *Server) handlePrefixLength() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { lengthStr := chi.URLParam(r, "length") @@ -779,22 +784,107 @@ func (s *Server) handlePrefixLength() http.HandlerFunc { return } - // Determine IP version based on mask length - const ( - maxIPv4MaskLength = 32 - maxIPv6MaskLength = 128 - ) - var ipVersion int - if maskLength <= maxIPv4MaskLength { - ipVersion = 4 - } else if maskLength <= maxIPv6MaskLength { - ipVersion = 6 - } else { + // Validate IPv4 mask length + const maxIPv4MaskLength = 32 + if maskLength < 0 || maskLength > maxIPv4MaskLength { + http.Error(w, "Invalid IPv4 mask length", http.StatusBadRequest) + + return + } + + const ipVersion = 4 + + // Get random sample of prefixes + const maxPrefixes = 500 + prefixes, err := s.db.GetRandomPrefixesByLengthContext(r.Context(), maskLength, ipVersion, maxPrefixes) + if err != nil { + s.logger.Error("Failed to get prefixes by length", "error", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + + return + } + + // Sort prefixes for display + sort.Slice(prefixes, func(i, j int) bool { + // First compare by IP version + if prefixes[i].IPVersion != prefixes[j].IPVersion { + return prefixes[i].IPVersion < prefixes[j].IPVersion + } + // Then by prefix + return prefixes[i].Prefix < prefixes[j].Prefix + }) + + // Create enhanced prefixes with AS descriptions + type EnhancedPrefix struct { + database.LiveRoute + OriginASDescription string + Age string + } + + enhancedPrefixes := make([]EnhancedPrefix, len(prefixes)) + for i, prefix := range prefixes { + enhancedPrefixes[i] = EnhancedPrefix{ + LiveRoute: prefix, + Age: formatAge(prefix.LastUpdated), + } + + // Get AS description + if asInfo, ok := asinfo.Get(prefix.OriginASN); ok { + enhancedPrefixes[i].OriginASDescription = asInfo.Description + } + } + + // Render template + data := map[string]interface{}{ + "MaskLength": maskLength, + "IPVersion": ipVersion, + "Prefixes": enhancedPrefixes, + "Count": len(prefixes), + } + + // Check if context is still valid before writing response + select { + case <-r.Context().Done(): + // Request was cancelled, don't write response + return + default: + } + + tmpl := templates.PrefixLengthTemplate() + if err := tmpl.Execute(w, data); err != nil { + s.logger.Error("Failed to render prefix length template", "error", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } + } +} + +// handlePrefixLength6 shows a random sample of IPv6 prefixes with the specified mask length +func (s *Server) handlePrefixLength6() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + lengthStr := chi.URLParam(r, "length") + if lengthStr == "" { + http.Error(w, "Length parameter is required", http.StatusBadRequest) + + return + } + + maskLength, err := strconv.Atoi(lengthStr) + if err != nil { http.Error(w, "Invalid mask length", http.StatusBadRequest) return } + // Validate IPv6 mask length + const maxIPv6MaskLength = 128 + if maskLength < 0 || maskLength > maxIPv6MaskLength { + http.Error(w, "Invalid IPv6 mask length", http.StatusBadRequest) + + return + } + + const ipVersion = 6 + // Get random sample of prefixes const maxPrefixes = 500 prefixes, err := s.db.GetRandomPrefixesByLengthContext(r.Context(), maskLength, ipVersion, maxPrefixes) diff --git a/internal/server/routes.go b/internal/server/routes.go index 1ba7356..848aeca 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -29,6 +29,7 @@ func (s *Server) setupRoutes() { r.Get("/as/{asn}", s.handleASDetail()) r.Get("/prefix/{prefix}", s.handlePrefixDetail()) r.Get("/prefixlength/{length}", s.handlePrefixLength()) + r.Get("/prefixlength6/{length}", s.handlePrefixLength6()) r.Get("/ip/{ip}", s.handleIPRedirect()) // API routes diff --git a/internal/templates/status.html b/internal/templates/status.html index 0f91747..9805db6 100644 --- a/internal/templates/status.html +++ b/internal/templates/status.html @@ -236,12 +236,16 @@ // Sort by mask length distribution.sort((a, b) => a.mask_length - b.mask_length); + // Determine the URL path based on whether this is IPv4 or IPv6 + const isIPv6 = elementId.includes('ipv6'); + const urlPath = isIPv6 ? '/prefixlength6/' : '/prefixlength/'; + distribution.forEach(item => { const metric = document.createElement('div'); metric.className = 'metric'; metric.innerHTML = ` /${item.mask_length} - ${formatNumber(item.count)} + ${formatNumber(item.count)} `; container.appendChild(metric); });