diff --git a/Makefile b/Makefile index 7790552..66956ff 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,2 @@ -srv: cmd/*.go - go build -o srv cmd/*.go +srv: cmd/*.go internal/routing/*.go + go build -o srv ./cmd/ diff --git a/cmd/processRequest.go b/cmd/processRequest.go index faba90b..cb2ce69 100644 --- a/cmd/processRequest.go +++ b/cmd/processRequest.go @@ -11,10 +11,12 @@ import ( "sync" "time" + "github.com/chubin/wttr.in/internal/routing" + lru "github.com/hashicorp/golang-lru" ) -type ResponseWithHeader struct { +type responseWithHeader struct { InProgress bool // true if the request is being processed Expires time.Time // expiration time of the cache entry @@ -29,7 +31,7 @@ type RequestProcessor struct { peakRequest60 sync.Map lruCache *lru.Cache stats *Stats - router Router + router routing.Router } // NewRequestProcessor returns new RequestProcessor. @@ -55,9 +57,9 @@ func (rp *RequestProcessor) Start() { rp.startPeakHandling() } -func (rp *RequestProcessor) ProcessRequest(r *http.Request) (*ResponseWithHeader, error) { +func (rp *RequestProcessor) ProcessRequest(r *http.Request) (*responseWithHeader, error) { var ( - response *ResponseWithHeader + response *responseWithHeader err error ) @@ -67,7 +69,7 @@ func (rp *RequestProcessor) ProcessRequest(r *http.Request) (*ResponseWithHeader if rh := rp.router.Route(r); rh != nil { result := rh.Response(r) if result != nil { - return result, nil + return fromCadre(result), nil } } @@ -90,7 +92,7 @@ func (rp *RequestProcessor) ProcessRequest(r *http.Request) (*ResponseWithHeader cacheBody, ok := rp.lruCache.Get(cacheDigest) if ok { rp.stats.Inc("cache1") - cacheEntry := cacheBody.(ResponseWithHeader) + cacheEntry := cacheBody.(responseWithHeader) // if after all attempts we still have no answer, // we try to make the query on our own @@ -101,7 +103,7 @@ func (rp *RequestProcessor) ProcessRequest(r *http.Request) (*ResponseWithHeader time.Sleep(30 * time.Millisecond) cacheBody, ok = rp.lruCache.Get(cacheDigest) if ok && cacheBody != nil { - cacheEntry = cacheBody.(ResponseWithHeader) + cacheEntry = cacheBody.(responseWithHeader) } } if cacheEntry.InProgress { @@ -123,7 +125,7 @@ func (rp *RequestProcessor) ProcessRequest(r *http.Request) (*ResponseWithHeader } } - rp.lruCache.Add(cacheDigest, ResponseWithHeader{InProgress: true}) + rp.lruCache.Add(cacheDigest, responseWithHeader{InProgress: true}) response, err = get(r) if err != nil { @@ -139,7 +141,7 @@ func (rp *RequestProcessor) ProcessRequest(r *http.Request) (*ResponseWithHeader return response, nil } -func get(req *http.Request) (*ResponseWithHeader, error) { +func get(req *http.Request) (*responseWithHeader, error) { client := &http.Client{} @@ -173,7 +175,7 @@ func get(req *http.Request) (*ResponseWithHeader, error) { return nil, err } - return &ResponseWithHeader{ + return &responseWithHeader{ InProgress: false, Expires: time.Now().Add(time.Duration(randInt(1000, 1500)) * time.Second), Body: body, @@ -214,7 +216,7 @@ func dontCache(req *http.Request) bool { // proxy_set_header X-Forwarded-Proto $scheme; // // -func redirectInsecure(req *http.Request) (*ResponseWithHeader, bool) { +func redirectInsecure(req *http.Request) (*responseWithHeader, bool) { if isPlainTextAgent(req.Header.Get("User-Agent")) { return nil, false } @@ -236,7 +238,7 @@ The document has moved `, target)) - return &ResponseWithHeader{ + return &responseWithHeader{ InProgress: false, Expires: time.Now().Add(time.Duration(randInt(1000, 1500)) * time.Second), Body: body, @@ -284,3 +286,14 @@ func ipFromAddr(s string) string { } return s[:pos] } + +// fromCadre converts Cadre into a responseWithHeader. +func fromCadre(cadre *routing.Cadre) *responseWithHeader { + return &responseWithHeader{ + Body: cadre.Body, + Expires: cadre.Expires, + StatusCode: 200, + InProgress: false, + } + +} diff --git a/cmd/route.go b/cmd/route.go deleted file mode 100644 index 37cc5cc..0000000 --- a/cmd/route.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import "net/http" - -type Handler interface { - Response(*http.Request) *ResponseWithHeader -} - -type routeFunc func(*http.Request) bool - -type route struct { - routeFunc - Handler -} - -type Router struct { - rt []route -} - -func (r *Router) Route(req *http.Request) Handler { - for _, re := range r.rt { - if re.routeFunc(req) { - return re.Handler - } - } - return nil -} - -func (r *Router) AddPath(path string, handler Handler) { - r.rt = append(r.rt, route{routePath(path), handler}) -} - -func routePath(path string) routeFunc { - return routeFunc(func(req *http.Request) bool { - return req.URL.Path == path - }) -} diff --git a/cmd/stat.go b/cmd/stat.go index 4006247..73e4647 100644 --- a/cmd/stat.go +++ b/cmd/stat.go @@ -6,6 +6,8 @@ import ( "net/http" "sync" "time" + + "github.com/chubin/wttr.in/internal/routing" ) // Stats holds processed requests statistics. @@ -77,9 +79,8 @@ func (c *Stats) Show() []byte { return b.Bytes() } -func (c *Stats) Response(*http.Request) *ResponseWithHeader { - return &ResponseWithHeader{ - Body: c.Show(), - StatusCode: 200, +func (c *Stats) Response(*http.Request) *routing.Cadre { + return &routing.Cadre{ + Body: c.Show(), } } diff --git a/go.mod b/go.mod index 7cf799a..001da54 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,8 @@ -module github.com/chubin/wttr.in/v2 +module github.com/chubin/wttr.in go 1.16 require ( github.com/hashicorp/golang-lru v0.6.0 github.com/robfig/cron v1.2.0 - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index fbffd34..d30f05d 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,3 @@ github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/routing/routing.go b/internal/routing/routing.go new file mode 100644 index 0000000..ba59566 --- /dev/null +++ b/internal/routing/routing.go @@ -0,0 +1,71 @@ +package routing + +import ( + "net/http" + "time" +) + +// CadreFormat specifies how the shot data is formatted. +type CadreFormat int + +const ( + // CadreFormatANSI represents Terminal ANSI format. + CadreFormatANSI = iota + + // CadreFormatHTML represents HTML. + CadreFormatHTML + + // CadreFormatPNG represents PNG. + CadreFormatPNG +) + +// Cadre contains result of a query execution. +type Cadre struct { + // Body contains the data of Cadre, formatted as Format. + Body []byte + + // Format of the shot. + Format CadreFormat + + // Expires contains the time of the Cadre expiration, + // or 0 if it does not expire. + Expires time.Time +} + +// Handler can handle queries and return views. +type Handler interface { + Response(*http.Request) *Cadre +} + +type routeFunc func(*http.Request) bool + +type route struct { + routeFunc + Handler +} + +// Router keeps a routing table, and finds queries handlers, based on its rules. +type Router struct { + rt []route +} + +// Route returns a query handler based on its content. +func (r *Router) Route(req *http.Request) Handler { + for _, re := range r.rt { + if re.routeFunc(req) { + return re.Handler + } + } + return nil +} + +// AddPath adds route for a static path. +func (r *Router) AddPath(path string, handler Handler) { + r.rt = append(r.rt, route{routePath(path), handler}) +} + +func routePath(path string) routeFunc { + return routeFunc(func(req *http.Request) bool { + return req.URL.Path == path + }) +}