Skip to content

Commit

Permalink
Use net/http router instead of chi (#215)
Browse files Browse the repository at this point in the history
  • Loading branch information
robherley authored Oct 15, 2024
1 parent 3d1c859 commit 28efebb
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 50 deletions.
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ require (
github.com/charmbracelet/ssh v0.0.0-20240725163421-eb71b85b27aa
github.com/charmbracelet/wish v1.4.3
github.com/dustin/go-humanize v1.0.1
github.com/go-chi/chi/v5 v5.1.0
github.com/jaevor/go-nanoid v1.4.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/klauspost/compress v1.17.10
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,6 @@ github.com/galeone/tensorflow/tensorflow/go v0.0.0-20221023090153-6b7fa0680c3e h
github.com/galeone/tensorflow/tensorflow/go v0.0.0-20221023090153-6b7fa0680c3e/go.mod h1:TelZuq26kz2jysARBwOrTv16629hyUsHmIoj54QqyFo=
github.com/galeone/tfgo v0.0.0-20230214145115-56cedbc50978 h1:8xhEVC2zjvI+3xWkt+78Krkd6JYp+0+iEoBVi0UBlJs=
github.com/galeone/tfgo v0.0.0-20230214145115-56cedbc50978/go.mod h1:3YgYBeIX42t83uP27Bd4bSMxTnQhSbxl0pYSkCDB1tc=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
Expand Down
18 changes: 10 additions & 8 deletions internal/http/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ var (
type Assets interface {
Doc(filename string) ([]byte, error)
Template() *template.Template
ServeJS(w http.ResponseWriter, r *http.Request)
ServeCSS(w http.ResponseWriter, r *http.Request)
Serve(w http.ResponseWriter, r *http.Request)
}

type StaticAssets struct {
Expand Down Expand Up @@ -137,12 +136,15 @@ func (a *StaticAssets) Template() *template.Template {
return a.tmpl
}

func (a *StaticAssets) ServeJS(w http.ResponseWriter, r *http.Request) {
serve(w, r, a.js, jsMime)
}

func (a *StaticAssets) ServeCSS(w http.ResponseWriter, r *http.Request) {
serve(w, r, a.css, cssMime)
func (a *StaticAssets) Serve(w http.ResponseWriter, r *http.Request) {
switch r.PathValue("asset") {
case "index.js":
serve(w, r, a.js, jsMime)
case "index.css":
serve(w, r, a.css, cssMime)
default:
http.NotFound(w, r)
}
}

// serve serves the content, gzipped if the client accepts it.
Expand Down
25 changes: 21 additions & 4 deletions internal/http/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import (
"encoding/json"
"html/template"
"net/http"
"net/http/pprof"
"net/url"
"strings"

"github.com/dustin/go-humanize"
"github.com/go-chi/chi/v5"
"github.com/robherley/snips.sh/internal/config"
"github.com/robherley/snips.sh/internal/db"
"github.com/robherley/snips.sh/internal/logger"
Expand All @@ -22,6 +22,24 @@ func HealthHandler(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("💚\n"))
}

func ProfileHandler(w http.ResponseWriter, r *http.Request) {
profiler := r.PathValue("profile")
switch profiler {
case "cmdline":
pprof.Cmdline(w, r)
case "profile":
pprof.Profile(w, r)
case "symbol":
pprof.Symbol(w, r)
case "trace":
pprof.Trace(w, r)
default:
// Available profiles can be found in [runtime/pprof.Profile].
// https://pkg.go.dev/runtime/pprof#Profile
pprof.Handler(profiler).ServeHTTP(w, r)
}
}

func MetaHandler(cfg *config.Config) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
Expand Down Expand Up @@ -53,7 +71,7 @@ func DocHandler(assets Assets) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log := logger.From(r.Context())

name := chi.URLParam(r, "name")
name := r.PathValue("name")
if name == "" {
name = "README.md"
}
Expand Down Expand Up @@ -94,8 +112,7 @@ func FileHandler(cfg *config.Config, database db.DB, assets Assets) http.Handler
return func(w http.ResponseWriter, r *http.Request) {
log := logger.From(r.Context())

fileID := chi.URLParam(r, "fileID")

fileID := r.PathValue("fileID")
if fileID == "" {
http.NotFound(w, r)
return
Expand Down
44 changes: 33 additions & 11 deletions internal/http/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,34 @@ package http
import (
"context"
"net/http"
"strings"
"time"

"github.com/armon/go-metrics"
"github.com/go-chi/chi/v5"
"github.com/robherley/snips.sh/internal/id"
"github.com/robherley/snips.sh/internal/logger"
"github.com/rs/zerolog/log"
)

type Middleware func(next http.Handler) http.Handler

var DefaultMiddleware = []Middleware{
WithRecover,
WithMetrics,
WithLogger,
WithRequestID,
}

func WithMiddleware(handler http.Handler, middlewares ...Middleware) http.Handler {
middlewares = append(DefaultMiddleware, middlewares...)

withMiddleware := handler
for i := range middlewares {
withMiddleware = middlewares[i](withMiddleware)
}
return withMiddleware
}

// WithRequestID adds a unique request ID to the request context.
func WithRequestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -40,9 +59,10 @@ func WithLogger(next http.Handler) http.Handler {
ctx := context.WithValue(r.Context(), logger.ContextKey, &reqLog)
reqLog.Info().Msg("connected")

next.ServeHTTP(w, r.WithContext(ctx))
r = r.WithContext(ctx)
next.ServeHTTP(w, r)

reqLog.Info().Dur("dur", time.Since(start)).Msg("disconnected")
reqLog.Info().Dur("dur", time.Since(start)).Str("pattern", Pattern(r)).Msg("disconnected")
})
}

Expand All @@ -65,19 +85,21 @@ func WithMetrics(next http.Handler) http.Handler {
start := time.Now()
next.ServeHTTP(w, r)

rctx := chi.RouteContext(r.Context())
pattern := rctx.RoutePattern()
if pattern == "" {
// empty pattern, didn't match router e.g. 404
pattern = "*"
}

labels := []metrics.Label{
{Name: "path", Value: pattern},
{Name: "pattern", Value: Pattern(r)},
{Name: "method", Value: r.Method},
}

metrics.IncrCounterWithLabels([]string{"http", "request"}, 1, labels)
metrics.MeasureSinceWithLabels([]string{"http", "request", "duration"}, start, labels)
})
}

func Pattern(r *http.Request) string {
pattern := strings.TrimPrefix(r.Pattern, r.Method+" ")
if pattern == "" {
// empty pattern, didn't match router e.g. 404
pattern = "*"
}
return pattern
}
37 changes: 14 additions & 23 deletions internal/http/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,32 @@ package http
import (
"net/http"

"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/robherley/snips.sh/internal/config"
"github.com/robherley/snips.sh/internal/db"
)

type Service struct {
*http.Server
Router *chi.Mux
}

func New(cfg *config.Config, database db.DB, assets Assets) (*Service, error) {
router := chi.NewRouter()
mux := http.NewServeMux()

router.Use(WithRequestID)
router.Use(WithLogger)
router.Use(WithMetrics)
router.Use(WithRecover)

router.Get("/", DocHandler(assets))
router.Get("/docs/{name}", DocHandler(assets))
router.Get("/health", HealthHandler)
router.Get("/f/{fileID}", FileHandler(cfg, database, assets))
router.Get("/assets/index.js", assets.ServeJS)
router.Get("/assets/index.css", assets.ServeCSS)
router.Get("/meta.json", MetaHandler(cfg))
mux.HandleFunc("GET /{$}", DocHandler(assets))
mux.HandleFunc("GET /docs/{name}", DocHandler(assets))
mux.HandleFunc("GET /health", HealthHandler)
mux.HandleFunc("GET /f/{fileID}", FileHandler(cfg, database, assets))
mux.HandleFunc("GET /assets/{asset}", assets.Serve)
mux.HandleFunc("GET /meta.json", MetaHandler(cfg))

if cfg.Debug {
router.Mount("/_debug", middleware.Profiler())
}

httpServer := &http.Server{
Addr: cfg.HTTP.Internal.Host,
Handler: router,
mux.HandleFunc("/_debug/pprof/{profile}", ProfileHandler)
}

return &Service{httpServer, router}, nil
return &Service{
&http.Server{
Addr: cfg.HTTP.Internal.Host,
Handler: WithMiddleware(mux),
},
}, nil
}
2 changes: 1 addition & 1 deletion internal/http/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (suite *HTTPServiceSuite) SetupTest() {
}

func (suite *HTTPServiceSuite) TestHTTPServer() {
ts := httptest.NewServer(suite.service.Router)
ts := httptest.NewServer(suite.service.Handler)
defer ts.Close()

signedFileID := "wdHzc62hsn"
Expand Down

0 comments on commit 28efebb

Please sign in to comment.