From e756b130991356fcbc6a83a1788115c6c89a943a Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Tue, 13 Oct 2015 11:06:02 -0700 Subject: [PATCH] Refactor handlers package Fixes #19. --- handlers/cors.go | 27 +++++ handlers/entities.go | 134 +++++++++++++++++++++++ handlers/error.go | 17 +++ handlers/handlers.go | 248 ------------------------------------------- handlers/json.go | 11 ++ handlers/token.go | 90 ++++++++++++++++ 6 files changed, 279 insertions(+), 248 deletions(-) create mode 100644 handlers/cors.go create mode 100644 handlers/entities.go create mode 100644 handlers/error.go create mode 100644 handlers/json.go create mode 100644 handlers/token.go diff --git a/handlers/cors.go b/handlers/cors.go new file mode 100644 index 0000000..eb74461 --- /dev/null +++ b/handlers/cors.go @@ -0,0 +1,27 @@ +package handlers + +import ( + "net/http" + "os" + "strings" +) + +func corsHandler(h http.Handler) http.Handler { + cors := func(w http.ResponseWriter, r *http.Request) { + domains := os.Getenv("DOMAINS") + allowedDomains := strings.Split(domains, ",") + if origin := r.Header.Get("Origin"); origin != "" { + for _, s := range allowedDomains { + if s == origin { + w.Header().Set("Access-Control-Allow-Origin", origin) + w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers")) + w.Header().Set("Access-Control-Allow-Methods", r.Header.Get("Access-Control-Request-Method")) + } + } + } + if r.Method != "OPTIONS" { + h.ServeHTTP(w, r) + } + } + return http.HandlerFunc(cors) +} diff --git a/handlers/entities.go b/handlers/entities.go new file mode 100644 index 0000000..0b1b2d1 --- /dev/null +++ b/handlers/entities.go @@ -0,0 +1,134 @@ +package handlers + +import ( + "io/ioutil" + "net/http" + "strconv" + + "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/gorilla/mux" + "github.com/thermokarst/bactdb/api" + "github.com/thermokarst/bactdb/helpers" + "github.com/thermokarst/bactdb/types" +) + +func handleGetter(g api.Getter) errorHandler { + return func(w http.ResponseWriter, r *http.Request) *types.AppError { + id, err := strconv.ParseInt(mux.Vars(r)["ID"], 10, 0) + if err != nil { + return newJSONError(err, http.StatusInternalServerError) + } + + claims := helpers.GetClaims(r) + + e, appErr := g.Get(id, mux.Vars(r)["genus"], &claims) + if appErr != nil { + return appErr + } + + data, err := e.Marshal() + if err != nil { + return newJSONError(err, http.StatusInternalServerError) + } + w.Write(data) + return nil + } +} + +func handleLister(l api.Lister) errorHandler { + return func(w http.ResponseWriter, r *http.Request) *types.AppError { + opt := r.URL.Query() + opt.Add("Genus", mux.Vars(r)["genus"]) + + claims := helpers.GetClaims(r) + + es, appErr := l.List(&opt, &claims) + if appErr != nil { + return appErr + } + data, err := es.Marshal() + if err != nil { + return newJSONError(err, http.StatusInternalServerError) + } + w.Write(data) + return nil + } +} + +func handleUpdater(u api.Updater) errorHandler { + return func(w http.ResponseWriter, r *http.Request) *types.AppError { + id, err := strconv.ParseInt(mux.Vars(r)["ID"], 10, 0) + if err != nil { + return newJSONError(err, http.StatusInternalServerError) + } + + bodyBytes, err := ioutil.ReadAll(r.Body) + if err != nil { + return newJSONError(err, http.StatusInternalServerError) + } + + e, err := u.Unmarshal(bodyBytes) + if err != nil { + return newJSONError(err, http.StatusInternalServerError) + } + + claims := helpers.GetClaims(r) + + appErr := u.Update(id, &e, mux.Vars(r)["genus"], &claims) + if appErr != nil { + return appErr + } + + data, err := e.Marshal() + if err != nil { + return newJSONError(err, http.StatusInternalServerError) + } + w.Write(data) + return nil + } +} + +func handleCreater(c api.Creater) errorHandler { + return func(w http.ResponseWriter, r *http.Request) *types.AppError { + bodyBytes, err := ioutil.ReadAll(r.Body) + if err != nil { + return newJSONError(err, http.StatusInternalServerError) + } + + e, err := c.Unmarshal(bodyBytes) + if err != nil { + return newJSONError(err, http.StatusInternalServerError) + } + + claims := helpers.GetClaims(r) + + appErr := c.Create(&e, mux.Vars(r)["genus"], &claims) + if appErr != nil { + return appErr + } + + data, err := e.Marshal() + if err != nil { + return newJSONError(err, http.StatusInternalServerError) + } + w.Write(data) + return nil + } +} + +func handleDeleter(d api.Deleter) errorHandler { + return func(w http.ResponseWriter, r *http.Request) *types.AppError { + id, err := strconv.ParseInt(mux.Vars(r)["ID"], 10, 0) + if err != nil { + return newJSONError(err, http.StatusInternalServerError) + } + + claims := helpers.GetClaims(r) + + appErr := d.Delete(id, mux.Vars(r)["genus"], &claims) + if appErr != nil { + return appErr + } + + return nil + } +} diff --git a/handlers/error.go b/handlers/error.go new file mode 100644 index 0000000..c617ce2 --- /dev/null +++ b/handlers/error.go @@ -0,0 +1,17 @@ +package handlers + +import ( + "fmt" + "net/http" + + "github.com/thermokarst/bactdb/types" +) + +type errorHandler func(http.ResponseWriter, *http.Request) *types.AppError + +func (fn errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if err := fn(w, r); err != nil { + w.WriteHeader(err.Status) + fmt.Fprintln(w, err.Error.Error()) + } +} diff --git a/handlers/handlers.go b/handlers/handlers.go index 50b1733..a006a7b 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -1,43 +1,14 @@ package handlers import ( - "encoding/json" - "fmt" - "io/ioutil" "net/http" - "net/http/httptest" - "os" - "strconv" - "strings" - "time" - "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/gorilla/context" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/gorilla/mux" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/nytimes/gziphandler" - "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/thermokarst/jwt" "github.com/thermokarst/bactdb/api" "github.com/thermokarst/bactdb/auth" - "github.com/thermokarst/bactdb/errors" - "github.com/thermokarst/bactdb/helpers" - "github.com/thermokarst/bactdb/models" - "github.com/thermokarst/bactdb/types" ) -func verifyClaims(claims []byte, r *http.Request) error { - // TODO: use helper - currentTime := time.Now() - var c types.Claims - err := json.Unmarshal(claims, &c) - if err != nil { - return err - } - if currentTime.After(time.Unix(c.Exp, 0)) { - return errors.ErrExpiredToken - } - context.Set(r, "claims", c) - return nil -} - // Handler is the root HTTP handler for bactdb. func Handler() http.Handler { m := mux.NewRouter() @@ -99,222 +70,3 @@ func Handler() http.Handler { return jsonHandler(gziphandler.GzipHandler(corsHandler(m))) } - -func handleGetter(g api.Getter) errorHandler { - return func(w http.ResponseWriter, r *http.Request) *types.AppError { - id, err := strconv.ParseInt(mux.Vars(r)["ID"], 10, 0) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - claims := helpers.GetClaims(r) - - e, appErr := g.Get(id, mux.Vars(r)["genus"], &claims) - if appErr != nil { - return appErr - } - - data, err := e.Marshal() - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - w.Write(data) - return nil - } -} - -func handleLister(l api.Lister) errorHandler { - return func(w http.ResponseWriter, r *http.Request) *types.AppError { - opt := r.URL.Query() - opt.Add("Genus", mux.Vars(r)["genus"]) - - claims := helpers.GetClaims(r) - - es, appErr := l.List(&opt, &claims) - if appErr != nil { - return appErr - } - data, err := es.Marshal() - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - w.Write(data) - return nil - } -} - -func handleUpdater(u api.Updater) errorHandler { - return func(w http.ResponseWriter, r *http.Request) *types.AppError { - id, err := strconv.ParseInt(mux.Vars(r)["ID"], 10, 0) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - bodyBytes, err := ioutil.ReadAll(r.Body) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - e, err := u.Unmarshal(bodyBytes) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - claims := helpers.GetClaims(r) - - appErr := u.Update(id, &e, mux.Vars(r)["genus"], &claims) - if appErr != nil { - return appErr - } - - data, err := e.Marshal() - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - w.Write(data) - return nil - } -} - -func handleCreater(c api.Creater) errorHandler { - return func(w http.ResponseWriter, r *http.Request) *types.AppError { - bodyBytes, err := ioutil.ReadAll(r.Body) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - e, err := c.Unmarshal(bodyBytes) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - claims := helpers.GetClaims(r) - - appErr := c.Create(&e, mux.Vars(r)["genus"], &claims) - if appErr != nil { - return appErr - } - - data, err := e.Marshal() - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - w.Write(data) - return nil - } -} - -func handleDeleter(d api.Deleter) errorHandler { - return func(w http.ResponseWriter, r *http.Request) *types.AppError { - id, err := strconv.ParseInt(mux.Vars(r)["ID"], 10, 0) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - - claims := helpers.GetClaims(r) - - appErr := d.Delete(id, mux.Vars(r)["genus"], &claims) - if appErr != nil { - return appErr - } - - return nil - } -} - -func tokenHandler(h http.Handler) http.Handler { - token := func(w http.ResponseWriter, r *http.Request) { - recorder := httptest.NewRecorder() - h.ServeHTTP(recorder, r) - - for key, val := range recorder.Header() { - w.Header()[key] = val - } - - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(recorder.Code) - - tokenData := string(recorder.Body.Bytes()) - - var data []byte - - if recorder.Code != 200 { - data, _ = json.Marshal(struct { - Error string `json:"error"` - }{ - Error: tokenData, - }) - } else { - data, _ = json.Marshal(struct { - Token string `json:"token"` - }{ - Token: tokenData, - }) - } - - w.Write(data) - return - - } - return http.HandlerFunc(token) -} - -func corsHandler(h http.Handler) http.Handler { - cors := func(w http.ResponseWriter, r *http.Request) { - domains := os.Getenv("DOMAINS") - allowedDomains := strings.Split(domains, ",") - if origin := r.Header.Get("Origin"); origin != "" { - for _, s := range allowedDomains { - if s == origin { - w.Header().Set("Access-Control-Allow-Origin", origin) - w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers")) - w.Header().Set("Access-Control-Allow-Methods", r.Header.Get("Access-Control-Request-Method")) - } - } - } - if r.Method != "OPTIONS" { - h.ServeHTTP(w, r) - } - } - return http.HandlerFunc(cors) -} - -func jsonHandler(h http.Handler) http.Handler { - j := func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - h.ServeHTTP(w, r) - } - return http.HandlerFunc(j) -} - -type errorHandler func(http.ResponseWriter, *http.Request) *types.AppError - -func (fn errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if err := fn(w, r); err != nil { - w.WriteHeader(err.Status) - fmt.Fprintln(w, err.Error.Error()) - } -} - -func tokenRefresh(j *jwt.Middleware) errorHandler { - t := func(w http.ResponseWriter, r *http.Request) *types.AppError { - claims := helpers.GetClaims(r) - user, err := models.GetUser(claims.Sub, "", &claims) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - user.Password = "" - token, err := auth.Middleware.CreateToken(user.Email) - if err != nil { - return newJSONError(err, http.StatusInternalServerError) - } - data, _ := json.Marshal(struct { - Token string `json:"token"` - }{ - Token: token, - }) - w.Write(data) - return nil - } - return t -} diff --git a/handlers/json.go b/handlers/json.go new file mode 100644 index 0000000..09acab3 --- /dev/null +++ b/handlers/json.go @@ -0,0 +1,11 @@ +package handlers + +import "net/http" + +func jsonHandler(h http.Handler) http.Handler { + j := func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + h.ServeHTTP(w, r) + } + return http.HandlerFunc(j) +} diff --git a/handlers/token.go b/handlers/token.go new file mode 100644 index 0000000..5b4b11f --- /dev/null +++ b/handlers/token.go @@ -0,0 +1,90 @@ +package handlers + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "time" + + "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/gorilla/context" + "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/thermokarst/jwt" + "github.com/thermokarst/bactdb/auth" + "github.com/thermokarst/bactdb/errors" + "github.com/thermokarst/bactdb/helpers" + "github.com/thermokarst/bactdb/models" + "github.com/thermokarst/bactdb/types" +) + +func verifyClaims(claims []byte, r *http.Request) error { + // TODO: use helper + currentTime := time.Now() + var c types.Claims + err := json.Unmarshal(claims, &c) + if err != nil { + return err + } + if currentTime.After(time.Unix(c.Exp, 0)) { + return errors.ErrExpiredToken + } + context.Set(r, "claims", c) + return nil +} +func tokenHandler(h http.Handler) http.Handler { + token := func(w http.ResponseWriter, r *http.Request) { + recorder := httptest.NewRecorder() + h.ServeHTTP(recorder, r) + + for key, val := range recorder.Header() { + w.Header()[key] = val + } + + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(recorder.Code) + + tokenData := string(recorder.Body.Bytes()) + + var data []byte + + if recorder.Code != 200 { + data, _ = json.Marshal(struct { + Error string `json:"error"` + }{ + Error: tokenData, + }) + } else { + data, _ = json.Marshal(struct { + Token string `json:"token"` + }{ + Token: tokenData, + }) + } + + w.Write(data) + return + + } + return http.HandlerFunc(token) +} + +func tokenRefresh(j *jwt.Middleware) errorHandler { + t := func(w http.ResponseWriter, r *http.Request) *types.AppError { + claims := helpers.GetClaims(r) + user, err := models.GetUser(claims.Sub, "", &claims) + if err != nil { + return newJSONError(err, http.StatusInternalServerError) + } + user.Password = "" + token, err := auth.Middleware.CreateToken(user.Email) + if err != nil { + return newJSONError(err, http.StatusInternalServerError) + } + data, _ := json.Marshal(struct { + Token string `json:"token"` + }{ + Token: token, + }) + w.Write(data) + return nil + } + return t +}