Skip to content

Commit e583db7

Browse files
authoredJun 8, 2023
Switch to chi for routing & add tests for http service (#52)
1 parent 2ab33a5 commit e583db7

14 files changed

+500
-144
lines changed
 

‎go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ require (
4545
github.com/dlclark/regexp2 v1.8.1 // indirect
4646
github.com/galeone/tensorflow/tensorflow/go v0.0.0-20221023090153-6b7fa0680c3e // indirect
4747
github.com/galeone/tfgo v0.0.0-20230214145115-56cedbc50978 // indirect
48+
github.com/go-chi/chi/v5 v5.0.8 // indirect
4849
github.com/go-openapi/inflect v0.19.0 // indirect
4950
github.com/google/go-cmp v0.5.9 // indirect
5051
github.com/gorilla/css v1.0.0 // indirect

‎go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ github.com/galeone/tensorflow/tensorflow/go v0.0.0-20221023090153-6b7fa0680c3e h
7070
github.com/galeone/tensorflow/tensorflow/go v0.0.0-20221023090153-6b7fa0680c3e/go.mod h1:TelZuq26kz2jysARBwOrTv16629hyUsHmIoj54QqyFo=
7171
github.com/galeone/tfgo v0.0.0-20230214145115-56cedbc50978 h1:8xhEVC2zjvI+3xWkt+78Krkd6JYp+0+iEoBVi0UBlJs=
7272
github.com/galeone/tfgo v0.0.0-20230214145115-56cedbc50978/go.mod h1:3YgYBeIX42t83uP27Bd4bSMxTnQhSbxl0pYSkCDB1tc=
73+
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
74+
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
7375
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
7476
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
7577
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=

‎internal/app/app.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func (app *App) shutdown(ctx context.Context) {
9292
wg.Wait()
9393
}
9494

95-
func New(cfg *config.Config, assets *http.Assets) (*App, error) {
95+
func New(cfg *config.Config, assets http.Assets) (*App, error) {
9696
database, err := db.NewSqlite(cfg.DB.FilePath)
9797
if err != nil {
9898
return nil, err

‎internal/http/assets.go

+47-25
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package http
22

33
import (
44
"compress/gzip"
5-
"embed"
65
"html/template"
6+
"io"
7+
"io/fs"
78
"net/http"
89
"os"
910
"path"
@@ -41,19 +42,38 @@ var (
4142
}
4243
)
4344

44-
type Assets struct {
45-
webFS *embed.FS
46-
docsFS *embed.FS
45+
type Assets interface {
46+
Doc(filename string) ([]byte, error)
47+
Template() *template.Template
48+
ServeJS(w http.ResponseWriter, r *http.Request)
49+
ServeCSS(w http.ResponseWriter, r *http.Request)
50+
}
51+
52+
type StaticAssets struct {
53+
webFS fs.FS
54+
docsFS fs.FS
4755
readme []byte
4856
css []byte
4957
js []byte
5058
tmpl *template.Template
5159
mini *minify.M
5260
}
5361

62+
func (a *StaticAssets) CSS() []byte {
63+
return a.css
64+
}
65+
66+
func (a *StaticAssets) JS() []byte {
67+
return a.js
68+
}
69+
70+
func (a *StaticAssets) README() []byte {
71+
return a.readme
72+
}
73+
5474
// NewAssets holds the templates, static content and minifies accordingly.
55-
func NewAssets(webFS *embed.FS, docsFS *embed.FS, readme []byte, extendHeadFile string) (*Assets, error) {
56-
assets := &Assets{
75+
func NewAssets(webFS fs.FS, docsFS fs.FS, readme []byte, extendHeadFile string) (*StaticAssets, error) {
76+
assets := &StaticAssets{
5777
webFS: webFS,
5878
docsFS: docsFS,
5979
readme: readme,
@@ -98,35 +118,30 @@ func NewAssets(webFS *embed.FS, docsFS *embed.FS, readme []byte, extendHeadFile
98118
return assets, nil
99119
}
100120

101-
func (a *Assets) JS() []byte {
102-
return a.js
103-
}
104-
105-
func (a *Assets) CSS() []byte {
106-
return a.css
107-
}
108-
109-
func (a *Assets) ReadME() []byte {
110-
return a.readme
111-
}
112-
113-
func (a *Assets) Doc(filename string) ([]byte, error) {
121+
func (a *StaticAssets) Doc(filename string) ([]byte, error) {
114122
if filename == "README.md" {
115123
return a.readme, nil
116124
}
117125

118-
return a.docsFS.ReadFile(path.Join(docsPath, filename))
126+
file, err := a.docsFS.Open(path.Join(docsPath, filename))
127+
if err != nil {
128+
return nil, err
129+
}
130+
131+
defer file.Close()
132+
133+
return io.ReadAll(file)
119134
}
120135

121-
func (a *Assets) Template() *template.Template {
136+
func (a *StaticAssets) Template() *template.Template {
122137
return a.tmpl
123138
}
124139

125-
func (a *Assets) ServeJS(w http.ResponseWriter, r *http.Request) {
140+
func (a *StaticAssets) ServeJS(w http.ResponseWriter, r *http.Request) {
126141
serve(w, r, a.js, jsMime)
127142
}
128143

129-
func (a *Assets) ServeCSS(w http.ResponseWriter, r *http.Request) {
144+
func (a *StaticAssets) ServeCSS(w http.ResponseWriter, r *http.Request) {
130145
serve(w, r, a.css, cssMime)
131146
}
132147

@@ -148,11 +163,18 @@ func serve(w http.ResponseWriter, r *http.Request, content []byte, contentType s
148163
}
149164

150165
// minify combines all the files and minifies them.
151-
func (a *Assets) minify(path string, files []string, mime string) ([]byte, error) {
166+
func (a *StaticAssets) minify(path string, files []string, mime string) ([]byte, error) {
152167
sb := strings.Builder{}
153168

154169
for _, file := range files {
155-
bites, err := a.webFS.ReadFile(filepath.Join(path, file))
170+
file, err := a.webFS.Open(filepath.Join(path, file))
171+
if err != nil {
172+
return nil, err
173+
}
174+
175+
defer file.Close()
176+
177+
bites, err := io.ReadAll(file)
156178
if err != nil {
157179
return nil, err
158180
}

‎internal/http/handlers.go

+11-33
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ import (
55
"html/template"
66
"net/http"
77
"net/url"
8-
"strconv"
98
"strings"
10-
"time"
119

1210
"github.com/dustin/go-humanize"
11+
"github.com/go-chi/chi/v5"
1312
"github.com/robherley/snips.sh/internal/config"
1413
"github.com/robherley/snips.sh/internal/db"
1514
"github.com/robherley/snips.sh/internal/logger"
@@ -50,10 +49,15 @@ func MetaHandler(cfg *config.Config) http.HandlerFunc {
5049
}
5150
}
5251

53-
func DocHandler(name string, assets *Assets) http.HandlerFunc {
52+
func DocHandler(assets Assets) http.HandlerFunc {
5453
return func(w http.ResponseWriter, r *http.Request) {
5554
log := logger.From(r.Context())
5655

56+
name := chi.URLParam(r, "name")
57+
if name == "" {
58+
name = "README.md"
59+
}
60+
5761
content, err := assets.Doc(name)
5862
if err != nil {
5963
log.Error().Err(err).Msg("unable to load file")
@@ -84,12 +88,13 @@ func DocHandler(name string, assets *Assets) http.HandlerFunc {
8488
}
8589
}
8690

87-
func FileHandler(cfg *config.Config, database db.DB, tmpl *template.Template) http.HandlerFunc {
91+
func FileHandler(cfg *config.Config, database db.DB, assets Assets) http.HandlerFunc {
8892
signer := signer.New(cfg.HMACKey)
93+
tmpl := assets.Template()
8994
return func(w http.ResponseWriter, r *http.Request) {
9095
log := logger.From(r.Context())
9196

92-
fileID := strings.TrimPrefix(r.URL.Path, "/f/")
97+
fileID := chi.URLParam(r, "fileID")
9398

9499
if fileID == "" {
95100
http.NotFound(w, r)
@@ -108,7 +113,7 @@ func FileHandler(cfg *config.Config, database db.DB, tmpl *template.Template) ht
108113
return
109114
}
110115

111-
isSignedAndNotExpired := IsSignedAndNotExpired(signer, r)
116+
isSignedAndNotExpired := signer.VerifyURLAndNotExpired(*r.URL)
112117

113118
if file.Private && !isSignedAndNotExpired {
114119
log.Warn().Msg("attempted to access private file")
@@ -186,33 +191,6 @@ func FileHandler(cfg *config.Config, database db.DB, tmpl *template.Template) ht
186191
}
187192
}
188193

189-
func IsSignedAndNotExpired(s *signer.Signer, r *http.Request) bool {
190-
if r.URL == nil {
191-
return false
192-
}
193-
194-
urlToVerify := url.URL{
195-
Path: r.URL.Path,
196-
RawQuery: r.URL.RawQuery,
197-
}
198-
199-
if !s.VerifyURL(urlToVerify) {
200-
return false
201-
}
202-
203-
exp := r.URL.Query().Get("exp")
204-
if exp == "" {
205-
return false
206-
}
207-
208-
expiresUnix, err := strconv.ParseInt(exp, 10, 64)
209-
if err != nil {
210-
return false
211-
}
212-
213-
return expiresUnix > time.Now().Unix()
214-
}
215-
216194
func ShouldSendRaw(r *http.Request) bool {
217195
if isCurl := strings.Contains(r.Header.Get("user-agent"), "curl"); isCurl {
218196
return true

‎internal/http/middleware.go

+25-11
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,13 @@ import (
55
"net/http"
66
"time"
77

8+
"github.com/armon/go-metrics"
9+
"github.com/go-chi/chi/v5"
810
"github.com/robherley/snips.sh/internal/id"
911
"github.com/robherley/snips.sh/internal/logger"
1012
"github.com/rs/zerolog/log"
1113
)
1214

13-
type Middleware func(next http.Handler) http.Handler
14-
15-
// WithMiddleware is a helper function to apply multiple middlewares to a handler.
16-
func WithMiddleware(handler http.Handler, middlewares ...Middleware) http.Handler {
17-
withMiddleware := handler
18-
for i := range middlewares {
19-
withMiddleware = middlewares[i](withMiddleware)
20-
}
21-
return withMiddleware
22-
}
23-
2415
// WithRequestID adds a unique request ID to the request context.
2516
func WithRequestID(next http.Handler) http.Handler {
2617
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -67,3 +58,26 @@ func WithRecover(next http.Handler) http.Handler {
6758
next.ServeHTTP(w, r)
6859
})
6960
}
61+
62+
// WithMetrics will record metrics for the request.
63+
func WithMetrics(next http.Handler) http.Handler {
64+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
65+
start := time.Now()
66+
next.ServeHTTP(w, r)
67+
68+
rctx := chi.RouteContext(r.Context())
69+
pattern := rctx.RoutePattern()
70+
if pattern == "" {
71+
// empty pattern, didn't match router e.g. 404
72+
pattern = "*"
73+
}
74+
75+
labels := []metrics.Label{
76+
{Name: "path", Value: pattern},
77+
{Name: "method", Value: r.Method},
78+
}
79+
80+
metrics.IncrCounterWithLabels([]string{"http", "request"}, 1, labels)
81+
metrics.MeasureSinceWithLabels([]string{"http", "request", "duration"}, start, labels)
82+
})
83+
}

‎internal/http/router.go

-41
This file was deleted.

0 commit comments

Comments
 (0)
Failed to load comments.