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);
});