this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(api) move server from handlers and utility functions

eagleusb 4e007981 609f1a97

+90 -73
+7 -6
internal/handlers/average.go
··· 8 8 "time" 9 9 10 10 "github.com/eagleusb/proxycon/internal/types" 11 + "github.com/eagleusb/proxycon/internal/utility" 11 12 ) 12 13 13 14 // averageEndpoint is the handler function for `GET /average?start=<timestamp>&end=<timestamp>`. 14 - func (h *HttpServer) averageEndpoint(w http.ResponseWriter, r *http.Request) { 15 + func (h *handler) averageEndpoint(w http.ResponseWriter, r *http.Request) { 15 16 startStr := r.URL.Query().Get("start") 16 17 endStr := r.URL.Query().Get("end") 17 18 18 19 if startStr == "" || endStr == "" { 19 - writeJSONError(w, "missing query parameters: start and end are required", http.StatusBadRequest) 20 + utility.WriteJSONError(w, "missing query parameters: start and end are required", http.StatusBadRequest) 20 21 return 21 22 } 22 23 23 24 startTime, err := time.Parse(time.RFC3339, startStr) 24 25 if err != nil { 25 - writeJSONError(w, "invalid start timestamp: must be RFC3339 format", http.StatusBadRequest) 26 + utility.WriteJSONError(w, "invalid start timestamp: must be RFC3339 format", http.StatusBadRequest) 26 27 return 27 28 } 28 29 29 30 endTime, err := time.Parse(time.RFC3339, endStr) 30 31 if err != nil { 31 - writeJSONError(w, "invalid end timestamp: must be RFC3339 format", http.StatusBadRequest) 32 + utility.WriteJSONError(w, "invalid end timestamp: must be RFC3339 format", http.StatusBadRequest) 32 33 return 33 34 } 34 35 35 36 if startTime.After(endTime) { 36 - writeJSONError(w, "start timestamp must be before end timestamp", http.StatusBadRequest) 37 + utility.WriteJSONError(w, "start timestamp must be before end timestamp", http.StatusBadRequest) 37 38 return 38 39 } 39 40 ··· 51 52 } 52 53 53 54 if len(matched) == 0 { 54 - writeJSONError(w, "no prices found in the given range", http.StatusNotFound) 55 + utility.WriteJSONError(w, "no prices found in the given range", http.StatusNotFound) 55 56 return 56 57 } 57 58
+3 -3
internal/handlers/error.go internal/utility/error.go
··· 1 - package handlers 1 + package utility 2 2 3 3 import ( 4 4 "encoding/json" ··· 7 7 "github.com/eagleusb/proxycon/internal/types" 8 8 ) 9 9 10 - // writeJSONError writes a JSON-formatted error response for consistency. 11 - func writeJSONError(w http.ResponseWriter, message string, code int) { 10 + // WriteJSONError writes a JSON-formatted error response for consistency. 11 + func WriteJSONError(w http.ResponseWriter, message string, code int) { 12 12 w.Header().Set("Content-Type", "application/json") 13 13 w.WriteHeader(code) 14 14 json.NewEncoder(w).Encode(types.ErrorResponse{Code: code, Message: message})
+24
internal/handlers/handler.go
··· 1 + package handlers 2 + 3 + import ( 4 + "net/http" 5 + 6 + "github.com/eagleusb/proxycon/internal/data" 7 + ) 8 + 9 + // handler holds dependencies for HTTP handlers. 10 + type handler struct { 11 + datasource *data.BitcoinPrices 12 + pageSize int 13 + } 14 + 15 + // Register registers all API handlers on the given mux. 16 + func Register(mux *http.ServeMux, datasource *data.BitcoinPrices, pageSize int) { 17 + h := &handler{ 18 + datasource: datasource, 19 + pageSize: pageSize, 20 + } 21 + mux.HandleFunc("GET /list", h.listEndpoint) 22 + mux.HandleFunc("GET /price", h.priceEndpoint) 23 + mux.HandleFunc("GET /average", h.averageEndpoint) 24 + }
+3 -2
internal/handlers/list.go
··· 7 7 "strconv" 8 8 9 9 "github.com/eagleusb/proxycon/internal/types" 10 + "github.com/eagleusb/proxycon/internal/utility" 10 11 ) 11 12 12 13 // listEndpoint is the handler function for `GET /list`. 13 - func (h *HttpServer) listEndpoint(w http.ResponseWriter, r *http.Request) { 14 + func (h *handler) listEndpoint(w http.ResponseWriter, r *http.Request) { 14 15 cursor := 0 15 16 16 17 if c := r.URL.Query().Get("cursor"); c != "" { 17 18 parsed, err := strconv.Atoi(c) 18 19 if err != nil || parsed < 0 { 19 - writeJSONError(w, "invalid cursor", http.StatusBadRequest) 20 + utility.WriteJSONError(w, "invalid cursor", http.StatusBadRequest) 20 21 return 21 22 } 22 23 cursor = parsed
+5 -4
internal/handlers/price.go
··· 7 7 "time" 8 8 9 9 "github.com/eagleusb/proxycon/internal/types" 10 + "github.com/eagleusb/proxycon/internal/utility" 10 11 ) 11 12 12 13 // priceEndpoint is the handler function for `GET /price?at=<timestamp>`. 13 - func (h *HttpServer) priceEndpoint(w http.ResponseWriter, r *http.Request) { 14 + func (h *handler) priceEndpoint(w http.ResponseWriter, r *http.Request) { 14 15 at := r.URL.Query().Get("at") 15 16 if at == "" { 16 - writeJSONError(w, "missing query parameter: at", http.StatusBadRequest) 17 + utility.WriteJSONError(w, "missing query parameter: at", http.StatusBadRequest) 17 18 return 18 19 } 19 20 20 21 if _, err := time.Parse(time.RFC3339, at); err != nil { 21 - writeJSONError(w, "invalid timestamp: must be RFC3339 format", http.StatusBadRequest) 22 + utility.WriteJSONError(w, "invalid timestamp: must be RFC3339 format", http.StatusBadRequest) 22 23 return 23 24 } 24 25 ··· 38 39 } 39 40 } 40 41 41 - writeJSONError(w, "no price found for the given timestamp", http.StatusNotFound) 42 + utility.WriteJSONError(w, "no price found for the given timestamp", http.StatusNotFound) 42 43 }
-54
internal/handlers/server.go
··· 1 - package handlers 2 - 3 - import ( 4 - "log/slog" 5 - "net/http" 6 - 7 - "github.com/eagleusb/proxycon/internal/data" 8 - ) 9 - 10 - // HttpServer holds dependencies for HTTP handlers. 11 - type HttpServer struct { 12 - datasource *data.BitcoinPrices 13 - pageSize int 14 - serveMux *http.ServeMux 15 - } 16 - 17 - // NewHttpServer returns the http api. 18 - func NewHttpServer(p *data.BitcoinPrices, pageSize int) *HttpServer { 19 - return &HttpServer{ 20 - datasource: p, 21 - pageSize: pageSize, 22 - serveMux: http.NewServeMux(), 23 - } 24 - } 25 - 26 - // Listen starts the http server with http2 support. 27 - func (h *HttpServer) Listen(addr string) error { 28 - h.registerHandler() 29 - 30 - s := &http.Server{ 31 - Addr: addr, 32 - Handler: h.serveMux, 33 - Protocols: &http.Protocols{}, 34 - } 35 - 36 - s.Protocols.SetHTTP1(true) 37 - s.Protocols.SetHTTP2(true) 38 - s.Protocols.SetUnencryptedHTTP2(true) 39 - 40 - slog.Debug("http server protocol", "http1", s.Protocols.HTTP1(), "http2", s.Protocols.HTTP2(), "unencrypted", s.Protocols.UnencryptedHTTP2()) 41 - 42 - if err := s.ListenAndServe(); err != nil { 43 - return err 44 - } 45 - 46 - return nil 47 - } 48 - 49 - // registerHandler registers all api handlers. 50 - func (h *HttpServer) registerHandler() { 51 - h.serveMux.HandleFunc("GET /list", h.listEndpoint) 52 - h.serveMux.HandleFunc("GET /price", h.priceEndpoint) 53 - h.serveMux.HandleFunc("GET /average", h.averageEndpoint) 54 - }
+44
internal/server/server.go
··· 1 + package server 2 + 3 + import ( 4 + "log/slog" 5 + "net/http" 6 + 7 + "github.com/eagleusb/proxycon/internal/data" 8 + "github.com/eagleusb/proxycon/internal/handlers" 9 + ) 10 + 11 + // Server holds the HTTP server configuration. 12 + type Server struct { 13 + serveMux *http.ServeMux 14 + } 15 + 16 + // NewServer returns a configured HTTP server with all routes registered. 17 + func NewServer(p *data.BitcoinPrices, pageSize int) *Server { 18 + mux := http.NewServeMux() 19 + handlers.Register(mux, p, pageSize) 20 + return &Server{ 21 + serveMux: mux, 22 + } 23 + } 24 + 25 + // Listen starts the HTTP server with HTTP/1 and HTTP/2 support. 26 + func (s *Server) Listen(addr string) error { 27 + srv := &http.Server{ 28 + Addr: addr, 29 + Handler: s.serveMux, 30 + Protocols: &http.Protocols{}, 31 + } 32 + 33 + srv.Protocols.SetHTTP1(true) 34 + srv.Protocols.SetHTTP2(true) 35 + srv.Protocols.SetUnencryptedHTTP2(true) 36 + 37 + slog.Debug("http server protocol", "http1", srv.Protocols.HTTP1(), "http2", srv.Protocols.HTTP2(), "unencrypted", srv.Protocols.UnencryptedHTTP2()) 38 + 39 + if err := srv.ListenAndServe(); err != nil { 40 + return err 41 + } 42 + 43 + return nil 44 + }
+4 -4
main.go
··· 8 8 "strconv" 9 9 "time" 10 10 11 - . "github.com/eagleusb/proxycon/internal/data" 12 - . "github.com/eagleusb/proxycon/internal/handlers" 11 + "github.com/eagleusb/proxycon/internal/data" 12 + "github.com/eagleusb/proxycon/internal/server" 13 13 ) 14 14 15 15 var ( ··· 42 42 logger := slog.New(slog.NewTextHandler(os.Stdout, opts)) 43 43 slog.SetDefault(logger) 44 44 45 - datasource := NewBitcoinPrices() 45 + datasource := data.NewBitcoinPrices() 46 46 ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) 47 47 defer cancel() 48 48 ··· 53 53 54 54 addr := ":" + strconv.Itoa(*port) 55 55 slog.Info("starting proxycon", "addr", addr, "page_size", *page, "version", version) 56 - if err := NewHttpServer(datasource, *page).Listen(addr); err != nil { 56 + if err := server.NewServer(datasource, *page).Listen(addr); err != nil { 57 57 slog.Error("proxycon init failed", "error", err) 58 58 os.Exit(1) 59 59 }