universal error handling in api pkg by returning errs from http handlers

This commit is contained in:
Danny 2018-04-25 11:59:30 +02:00
parent 08ce5e651d
commit b3ee15cc9a
11 changed files with 116 additions and 71 deletions

View File

@ -25,7 +25,7 @@ type login struct {
var store = sessions.NewCookieStore([]byte(os.Getenv("ANA_SECRET_KEY"))) var store = sessions.NewCookieStore([]byte(os.Getenv("ANA_SECRET_KEY")))
// URL: POST /api/session // URL: POST /api/session
var LoginHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var LoginHandler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
// check login creds // check login creds
var l login var l login
@ -36,27 +36,31 @@ var LoginHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request)
// compare pwd // compare pwd
if err != nil || bcrypt.CompareHashAndPassword([]byte(u.HashedPassword), []byte(l.Password)) != nil { if err != nil || bcrypt.CompareHashAndPassword([]byte(u.HashedPassword), []byte(l.Password)) != nil {
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
respond(w, envelope{Error: "invalid_credentials"}) return respond(w, envelope{Error: "invalid_credentials"})
return
} }
session, _ := store.Get(r, "auth") session, _ := store.Get(r, "auth")
session.Values["user_id"] = u.ID session.Values["user_id"] = u.ID
err = session.Save(r, w) err = session.Save(r, w)
checkError(err) if err != nil {
return err
}
respond(w, envelope{Data: true}) return respond(w, envelope{Data: true})
}) })
// URL: DELETE /api/session // URL: DELETE /api/session
var LogoutHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var LogoutHandler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
session, _ := store.Get(r, "auth") session, _ := store.Get(r, "auth")
if !session.IsNew { if !session.IsNew {
session.Options.MaxAge = -1 session.Options.MaxAge = -1
session.Save(r, w) err := session.Save(r, w)
if err != nil {
return err
}
} }
respond(w, envelope{Data: true}) return respond(w, envelope{Data: true})
}) })
/* middleware */ /* middleware */

View File

@ -6,8 +6,8 @@ import (
) )
// URL: /api/browsers // URL: /api/browsers
var GetBrowsersHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var GetBrowsersHandler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
before, after := getRequestedPeriods(r) before, after := getRequestedPeriods(r)
results := count.Browsers(before, after, getRequestedLimit(r)) results := count.Browsers(before, after, getRequestedLimit(r))
respond(w, envelope{Data: results}) return respond(w, envelope{Data: results})
}) })

View File

@ -11,28 +11,23 @@ import (
"github.com/mssola/user_agent" "github.com/mssola/user_agent"
"github.com/usefathom/fathom/pkg/datastore" "github.com/usefathom/fathom/pkg/datastore"
"github.com/usefathom/fathom/pkg/models" "github.com/usefathom/fathom/pkg/models"
log "github.com/sirupsen/logrus"
) )
var buffer []*models.Pageview var buffer []*models.Pageview
var bufferSize = 250 var bufferSize = 250
var timeout = 100 * time.Millisecond var timeout = 100 * time.Millisecond
func getRequestIp(r *http.Request) string {
ipAddress := r.RemoteAddr
headerForwardedFor := r.Header.Get("X-Forwarded-For")
if headerForwardedFor != "" {
ipAddress = headerForwardedFor
}
return ipAddress
}
func persistPageviews() { func persistPageviews() {
if len(buffer) > 0 { if len(buffer) > 0 {
err := datastore.SavePageviews(buffer) err := datastore.SavePageviews(buffer)
if err != nil {
log.Errorf("error saving pageviews: %s", err)
}
// clear buffer regardless of error... this means data loss, but better than filling the buffer for now
buffer = buffer[:0] buffer = buffer[:0]
checkError(err)
} }
} }
@ -55,12 +50,12 @@ func NewCollectHandler() http.Handler {
pageviews := make(chan *models.Pageview, 100) pageviews := make(chan *models.Pageview, 100)
go processBuffer(pageviews) go processBuffer(pageviews)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
ua := user_agent.New(r.UserAgent())
// abort if this is a bot. // abort if this is a bot.
ua := user_agent.New(r.UserAgent())
if ua.Bot() { if ua.Bot() {
return return nil
} }
q := r.URL.Query() q := r.URL.Query()
@ -75,7 +70,9 @@ func NewCollectHandler() http.Handler {
} }
err = datastore.SavePage(page) err = datastore.SavePage(page)
checkError(err) if err != nil {
return err
}
} }
// find or insert visitor. // find or insert visitor.
@ -98,7 +95,9 @@ func NewCollectHandler() http.Handler {
visitor.BrowserName, visitor.BrowserVersion = ua.Browser() visitor.BrowserName, visitor.BrowserVersion = ua.Browser()
visitor.BrowserName = parseMajorMinor(visitor.BrowserName) visitor.BrowserName = parseMajorMinor(visitor.BrowserName)
err = datastore.SaveVisitor(visitor) err = datastore.SaveVisitor(visitor)
checkError(err) if err != nil {
return err
}
} }
pageview := &models.Pageview{ pageview := &models.Pageview{
@ -127,6 +126,7 @@ func NewCollectHandler() http.Handler {
// 1x1 px transparent GIF // 1x1 px transparent GIF
b, _ := base64.StdEncoding.DecodeString("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") b, _ := base64.StdEncoding.DecodeString("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")
w.Write(b) w.Write(b)
return nil
}) })
} }

43
pkg/api/http.go Normal file
View File

@ -0,0 +1,43 @@
package api
import (
"encoding/json"
log "github.com/sirupsen/logrus"
"net/http"
)
// Handler is our custom HTTP handler with error returns
type Handler func(w http.ResponseWriter, r *http.Request) error
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := h(w, r); err != nil {
HandleError(w, r, err)
}
}
// HandlerFunc takes a custom Handler func and converts it to http.HandlerFunc
func HandlerFunc(fn Handler) http.HandlerFunc {
return http.HandlerFunc(Handler(fn).ServeHTTP)
}
// HandleError handles errors
func HandleError(w http.ResponseWriter, r *http.Request, err error) {
log.WithFields(log.Fields{
"request": r.Method + " " + r.RequestURI,
"error": err,
}).Error("error handling request")
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("false"))
}
type envelope struct {
Data interface{}
Error interface{} `json:"omitempty"`
}
func respond(w http.ResponseWriter, d interface{}) error {
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(d)
return err
}

View File

@ -7,8 +7,8 @@ import (
) )
// URL: /api/languages // URL: /api/languages
var GetLanguagesHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var GetLanguagesHandler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
before, after := getRequestedPeriods(r) before, after := getRequestedPeriods(r)
results := count.Languages(before, after, getRequestedLimit(r)) results := count.Languages(before, after, getRequestedLimit(r))
respond(w, envelope{Data: results}) return respond(w, envelope{Data: results})
}) })

View File

@ -15,7 +15,7 @@ type pageviews struct {
} }
// URL: /api/pageviews // URL: /api/pageviews
var GetPageviewsHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var GetPageviewsHandler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
before, after := getRequestedPeriods(r) before, after := getRequestedPeriods(r)
stmt, err := datastore.DB.Prepare(` stmt, err := datastore.DB.Prepare(`
@ -30,38 +30,46 @@ var GetPageviewsHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.R
GROUP BY p.path, p.hostname GROUP BY p.path, p.hostname
ORDER BY count DESC ORDER BY count DESC
LIMIT ?`) LIMIT ?`)
if err != nil {
checkError(err) return err
}
defer stmt.Close() defer stmt.Close()
rows, err := stmt.Query(before, after, defaultLimit) rows, err := stmt.Query(before, after, defaultLimit)
checkError(err) if err != nil {
return err
}
defer rows.Close() defer rows.Close()
results := make([]pageviews, 0) results := make([]pageviews, 0)
for rows.Next() { for rows.Next() {
var p pageviews var p pageviews
err = rows.Scan(&p.Hostname, &p.Path, &p.Count, &p.CountUnique) err = rows.Scan(&p.Hostname, &p.Path, &p.Count, &p.CountUnique)
checkError(err) if err != nil {
return err
}
results = append(results, p) results = append(results, p)
} }
err = rows.Err() err = rows.Err()
checkError(err) if err != nil {
return err
}
respond(w, envelope{Data: results}) return respond(w, envelope{Data: results})
}) })
// URL: /api/pageviews/count // URL: /api/pageviews/count
var GetPageviewsCountHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var GetPageviewsCountHandler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
before, after := getRequestedPeriods(r) before, after := getRequestedPeriods(r)
result := count.Pageviews(before, after) result := count.Pageviews(before, after)
respond(w, envelope{Data: result}) return respond(w, envelope{Data: result})
}) })
// URL: /api/pageviews/group/day // URL: /api/pageviews/group/day
var GetPageviewsPeriodCountHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var GetPageviewsPeriodCountHandler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
before, after := getRequestedPeriods(r) before, after := getRequestedPeriods(r)
results := count.PageviewsPerDay(before, after) results := count.PageviewsPerDay(before, after)
respond(w, envelope{Data: results}) return respond(w, envelope{Data: results})
}) })

View File

@ -1,8 +1,6 @@
package api package api
import ( import (
"encoding/json"
"log"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -12,25 +10,6 @@ import (
const defaultPeriod = 7 const defaultPeriod = 7
const defaultLimit = 10 const defaultLimit = 10
type envelope struct {
Data interface{}
Error interface{}
}
func respond(w http.ResponseWriter, d interface{}) {
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
err := enc.Encode(d)
checkError(err)
}
// log fatal errors
func checkError(err error) {
if err != nil {
log.Fatal(err)
}
}
func getRequestedLimit(r *http.Request) int { func getRequestedLimit(r *http.Request) int {
limit, err := strconv.Atoi(r.URL.Query().Get("limit")) limit, err := strconv.Atoi(r.URL.Query().Get("limit"))
if err != nil || limit == 0 { if err != nil || limit == 0 {
@ -64,3 +43,14 @@ func parseMajorMinor(v string) string {
} }
return v return v
} }
func getRequestIp(r *http.Request) string {
ipAddress := r.RemoteAddr
headerForwardedFor := r.Header.Get("X-Forwarded-For")
if headerForwardedFor != "" {
ipAddress = headerForwardedFor
}
return ipAddress
}

View File

@ -7,8 +7,8 @@ import (
) )
// URL: /api/referrers // URL: /api/referrers
var GetReferrersHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var GetReferrersHandler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
before, after := getRequestedPeriods(r) before, after := getRequestedPeriods(r)
results := count.Referrers(before, after, getRequestedLimit(r)) results := count.Referrers(before, after, getRequestedLimit(r))
respond(w, envelope{Data: results}) return respond(w, envelope{Data: results})
}) })

View File

@ -7,8 +7,8 @@ import (
) )
// URL: /api/screen-resolutions // URL: /api/screen-resolutions
var GetScreenResolutionsHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var GetScreenResolutionsHandler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
before, after := getRequestedPeriods(r) before, after := getRequestedPeriods(r)
results := count.Screens(before, after, getRequestedLimit(r)) results := count.Screens(before, after, getRequestedLimit(r))
respond(w, envelope{Data: results}) return respond(w, envelope{Data: results})
}) })

View File

@ -7,21 +7,21 @@ import (
) )
// URL: /api/visitors/count // URL: /api/visitors/count
var GetVisitorsCountHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var GetVisitorsCountHandler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
before, after := getRequestedPeriods(r) before, after := getRequestedPeriods(r)
result := count.Visitors(before, after) result := count.Visitors(before, after)
respond(w, envelope{Data: result}) return respond(w, envelope{Data: result})
}) })
// URL: /api/visitors/count/realtime // URL: /api/visitors/count/realtime
var GetVisitorsRealtimeCountHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var GetVisitorsRealtimeCountHandler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
result := count.RealtimeVisitors() result := count.RealtimeVisitors()
respond(w, envelope{Data: result}) return respond(w, envelope{Data: result})
}) })
// URL: /api/visitors/count/group/:period // URL: /api/visitors/count/group/:period
var GetVisitorsPeriodCountHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var GetVisitorsPeriodCountHandler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
before, after := getRequestedPeriods(r) before, after := getRequestedPeriods(r)
results := count.VisitorsPerDay(before, after) results := count.VisitorsPerDay(before, after)
respond(w, envelope{Data: results}) return respond(w, envelope{Data: results})
}) })