diff --git a/customers.go b/customers.go index 80ac15ec3..f9bc6fd33 100644 --- a/customers.go +++ b/customers.go @@ -13,6 +13,8 @@ import ( "strings" "time" + moovhttp "github.com/moov-io/base/http" + "github.com/go-kit/kit/log" "github.com/gorilla/mux" ) @@ -128,10 +130,10 @@ func getUserCustomers(customerRepo customerRepository) http.HandlerFunc { return } - userId := getUserId(r) + userId := moovhttp.GetUserId(r) customers, err := customerRepo.getUserCustomers(userId) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } @@ -139,7 +141,7 @@ func getUserCustomers(customerRepo customerRepository) http.HandlerFunc { w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(customers); err != nil { - internalError(w, err, "getUserCustomers") + internalError(w, err) return } } @@ -169,13 +171,13 @@ func createUserCustomer(customerRepo customerRepository, depositoryRepo deposito req, err := readCustomerRequest(r) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } - userId := getUserId(r) + userId := moovhttp.GetUserId(r) if !depositoryIdExists(userId, req.DefaultDepository, depositoryRepo) { - encodeError(w, fmt.Errorf("Depository %s does not exist", req.DefaultDepository)) + moovhttp.Problem(w, fmt.Errorf("Depository %s does not exist", req.DefaultDepository)) return } @@ -189,11 +191,11 @@ func createUserCustomer(customerRepo customerRepository, depositoryRepo deposito Created: time.Now(), } if err := customer.validate(); err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } if err := customerRepo.upsertUserCustomer(userId, customer); err != nil { - internalError(w, fmt.Errorf("creating customer=%q, user_id=%q", customer.ID, userId), "customers") + internalError(w, fmt.Errorf("creating customer=%q, user_id=%q", customer.ID, userId)) return } @@ -201,7 +203,7 @@ func createUserCustomer(customerRepo customerRepository, depositoryRepo deposito w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(customer); err != nil { - internalError(w, err, "createUserCustomer") + internalError(w, err) return } } @@ -214,7 +216,7 @@ func getUserCustomer(customerRepo customerRepository) http.HandlerFunc { return } - id, userId := getCustomerId(r), getUserId(r) + id, userId := getCustomerId(r), moovhttp.GetUserId(r) if id == "" { w.WriteHeader(http.StatusNotFound) return @@ -222,7 +224,7 @@ func getUserCustomer(customerRepo customerRepository) http.HandlerFunc { customer, err := customerRepo.getUserCustomer(id, userId) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } @@ -230,7 +232,7 @@ func getUserCustomer(customerRepo customerRepository) http.HandlerFunc { w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(customer); err != nil { - internalError(w, err, "getUserCustomer") + internalError(w, err) return } } @@ -245,11 +247,11 @@ func updateUserCustomer(customerRepo customerRepository) http.HandlerFunc { req, err := readCustomerRequest(r) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } - id, userId := getCustomerId(r), getUserId(r) + id, userId := getCustomerId(r), moovhttp.GetUserId(r) if id == "" { w.WriteHeader(http.StatusNotFound) return @@ -257,7 +259,7 @@ func updateUserCustomer(customerRepo customerRepository) http.HandlerFunc { customer, err := customerRepo.getUserCustomer(id, userId) if err != nil { - internalError(w, fmt.Errorf("problem getting customer=%q, user_id=%q", id, userId), "customers") + internalError(w, fmt.Errorf("problem getting customer=%q, user_id=%q", id, userId)) return } if req.DefaultDepository != "" { @@ -269,13 +271,13 @@ func updateUserCustomer(customerRepo customerRepository) http.HandlerFunc { customer.Updated = time.Now() if err := customer.validate(); err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } // Perform update if err := customerRepo.upsertUserCustomer(userId, customer); err != nil { - internalError(w, fmt.Errorf("updating customer=%q, user_id=%q", id, userId), "customers") + internalError(w, fmt.Errorf("updating customer=%q, user_id=%q", id, userId)) return } @@ -283,7 +285,7 @@ func updateUserCustomer(customerRepo customerRepository) http.HandlerFunc { w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(customer); err != nil { - internalError(w, err, "updateUserCustomer") + internalError(w, err) return } } @@ -296,14 +298,14 @@ func deleteUserCustomer(customerRepo customerRepository) http.HandlerFunc { return } - id, userId := getCustomerId(r), getUserId(r) + id, userId := getCustomerId(r), moovhttp.GetUserId(r) if id == "" { w.WriteHeader(http.StatusNotFound) return } if err := customerRepo.deleteUserCustomer(id, userId); err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } diff --git a/depositories.go b/depositories.go index 2490e9560..66c451afd 100644 --- a/depositories.go +++ b/depositories.go @@ -12,6 +12,8 @@ import ( "strings" "time" + moovhttp "github.com/moov-io/base/http" + "github.com/go-kit/kit/log" "github.com/gorilla/mux" ) @@ -189,10 +191,10 @@ func getUserDepositories(depositoryRepo depositoryRepository) http.HandlerFunc { return } - userId := getUserId(r) + userId := moovhttp.GetUserId(r) deposits, err := depositoryRepo.getUserDepositories(userId) if err != nil { - internalError(w, err, "getUserDepositories") + internalError(w, err) return } @@ -200,7 +202,7 @@ func getUserDepositories(depositoryRepo depositoryRepository) http.HandlerFunc { w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(deposits); err != nil { - internalError(w, err, "getUserDepositories") + internalError(w, err) return } } @@ -233,11 +235,11 @@ func createUserDepository(depositoryRepo depositoryRepository) http.HandlerFunc req, err := readDepositoryRequest(r) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } - userId, now := getUserId(r), time.Now() + userId, now := moovhttp.GetUserId(r), time.Now() depository := &Depository{ ID: DepositoryID(nextID()), BankName: req.BankName, @@ -254,12 +256,12 @@ func createUserDepository(depositoryRepo depositoryRepository) http.HandlerFunc } if err := depository.validate(); err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } if err := depositoryRepo.upsertUserDepository(userId, depository); err != nil { - internalError(w, err, "createUserDepository") + internalError(w, err) return } @@ -267,7 +269,7 @@ func createUserDepository(depositoryRepo depositoryRepository) http.HandlerFunc w.WriteHeader(http.StatusCreated) if err := json.NewEncoder(w).Encode(depository); err != nil { - internalError(w, err, "createUserDepository") + internalError(w, err) return } } @@ -280,14 +282,14 @@ func getUserDepository(depositoryRepo depositoryRepository) http.HandlerFunc { return } - id, userId := getDepositoryId(r), getUserId(r) + id, userId := getDepositoryId(r), moovhttp.GetUserId(r) if id == "" { w.WriteHeader(http.StatusNotFound) return } depository, err := depositoryRepo.getUserDepository(id, userId) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } @@ -295,7 +297,7 @@ func getUserDepository(depositoryRepo depositoryRepository) http.HandlerFunc { w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(depository); err != nil { - internalError(w, err, "getUserDepository") + internalError(w, err) return } } @@ -310,11 +312,11 @@ func updateUserDepository(depositoryRepo depositoryRepository) http.HandlerFunc req, err := readDepositoryRequest(r) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } - id, userId := getDepositoryId(r), getUserId(r) + id, userId := getDepositoryId(r), moovhttp.GetUserId(r) if id == "" { w.WriteHeader(http.StatusNotFound) return @@ -322,7 +324,7 @@ func updateUserDepository(depositoryRepo depositoryRepository) http.HandlerFunc depository, err := depositoryRepo.getUserDepository(id, userId) if err != nil { - internalError(w, err, "depositories") + internalError(w, err) return } if depository == nil { @@ -358,12 +360,12 @@ func updateUserDepository(depositoryRepo depositoryRepository) http.HandlerFunc depository.Updated = time.Now() if err := depository.validate(); err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } if err := depositoryRepo.upsertUserDepository(userId, depository); err != nil { - internalError(w, err, "updateUserDepository") + internalError(w, err) return } @@ -371,7 +373,7 @@ func updateUserDepository(depositoryRepo depositoryRepository) http.HandlerFunc w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(depository); err != nil { - internalError(w, err, "updateUserDepository") + internalError(w, err) return } } @@ -384,14 +386,14 @@ func deleteUserDepository(depositoryRepo depositoryRepository) http.HandlerFunc return } - id, userId := getDepositoryId(r), getUserId(r) + id, userId := getDepositoryId(r), moovhttp.GetUserId(r) if id == "" { w.WriteHeader(http.StatusNotFound) return } if err := depositoryRepo.deleteUserDepository(id, userId); err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } diff --git a/events.go b/events.go index 14158e95e..b3feec3da 100644 --- a/events.go +++ b/events.go @@ -10,6 +10,8 @@ import ( "net/http" "time" + moovhttp "github.com/moov-io/base/http" + "github.com/go-kit/kit/log" "github.com/gorilla/mux" ) @@ -48,10 +50,10 @@ func getUserEvents(eventRepo eventRepository) http.HandlerFunc { return } - userId := getUserId(r) + userId := moovhttp.GetUserId(r) events, err := eventRepo.getUserEvents(userId) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } @@ -59,7 +61,7 @@ func getUserEvents(eventRepo eventRepository) http.HandlerFunc { w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(events); err != nil { - internalError(w, err, "events") + internalError(w, err) return } } @@ -72,7 +74,7 @@ func getEventHandler(eventRepo eventRepository) http.HandlerFunc { return } - eventId, userId := getEventId(r), getUserId(r) + eventId, userId := getEventId(r), moovhttp.GetUserId(r) if eventId == "" { w.WriteHeader(http.StatusBadRequest) return @@ -81,7 +83,7 @@ func getEventHandler(eventRepo eventRepository) http.HandlerFunc { // grab event event, err := eventRepo.getEvent(eventId, userId) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } @@ -89,7 +91,7 @@ func getEventHandler(eventRepo eventRepository) http.HandlerFunc { w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(event); err != nil { - internalError(w, err, "events") + internalError(w, err) return } } diff --git a/gateways.go b/gateways.go index 142a78f71..f142d2be2 100644 --- a/gateways.go +++ b/gateways.go @@ -12,6 +12,8 @@ import ( "strings" "time" + moovhttp "github.com/moov-io/base/http" + "github.com/go-kit/kit/log" "github.com/gorilla/mux" ) @@ -72,10 +74,10 @@ func getUserGateway(gatewayRepo gatewayRepository) http.HandlerFunc { return } - userId := getUserId(r) + userId := moovhttp.GetUserId(r) gateway, err := gatewayRepo.getUserGateway(userId) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } @@ -83,7 +85,7 @@ func getUserGateway(gatewayRepo gatewayRepository) http.HandlerFunc { w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(gateway); err != nil { - internalError(w, err, "getUserGateway") + internalError(w, err) return } } @@ -98,24 +100,24 @@ func createUserGateway(gatewayRepo gatewayRepository) http.HandlerFunc { bs, err := read(r.Body) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } var req gatewayRequest if err := json.Unmarshal(bs, &req); err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } if req.missingFields() { - encodeError(w, errMissingRequiredJson) + moovhttp.Problem(w, errMissingRequiredJson) return } - userId := getUserId(r) + userId := moovhttp.GetUserId(r) gateway, err := gatewayRepo.createUserGateway(userId, req) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } @@ -123,7 +125,7 @@ func createUserGateway(gatewayRepo gatewayRepository) http.HandlerFunc { w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(gateway); err != nil { - internalError(w, err, "createUserGateway") + internalError(w, err) return } } diff --git a/go.mod b/go.mod index 6819e4539..60dfce4ca 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,11 @@ module github.com/moov-io/paygate require ( github.com/go-kit/kit v0.8.0 github.com/go-logfmt/logfmt v0.4.0 // indirect - github.com/gogo/protobuf v1.1.1 // indirect github.com/gorilla/mux v1.6.2 - github.com/hashicorp/golang-lru v0.5.0 + github.com/hashicorp/golang-lru v0.5.0 // indirect github.com/mattn/go-sqlite3 v1.10.0 github.com/moov-io/ach v0.5.0 - github.com/moov-io/base v0.0.0-20181127010612-e1e22f6949b7 - github.com/prometheus/client_golang v0.9.1 - github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 // indirect - github.com/prometheus/procfs v0.0.0-20181129180645-aa55a523dc0a // indirect - golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect + github.com/moov-io/base v0.0.0-20181203201558-b27bcdd66705 + github.com/prometheus/client_golang v0.9.2 golang.org/x/text v0.3.0 ) diff --git a/go.sum b/go.sum index 086558374..96a3f8940 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,6 @@ github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80n github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= @@ -30,11 +28,11 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/moov-io/ach v0.5.0 h1:dCsVKDkZN7QcFhcbgPYka8TqtcG9cOz42YljMd8tJTg= github.com/moov-io/ach v0.5.0/go.mod h1:1Gya3bnXx2JVjCewOxF+QqKRKEXiuqf7mAbkYEwzEIg= github.com/moov-io/base v0.0.0-20181030202447-dedcf5cc8055/go.mod h1:pPu/TAc9PkaaegbREVEeDHsGqyAlvji9vqTuARuAnd0= -github.com/moov-io/base v0.0.0-20181127010612-e1e22f6949b7 h1:AjVhffB5QxPSGhJItbRcHujYQ11xaR+ZX9TuqW71/3w= -github.com/moov-io/base v0.0.0-20181127010612-e1e22f6949b7/go.mod h1:pPu/TAc9PkaaegbREVEeDHsGqyAlvji9vqTuARuAnd0= +github.com/moov-io/base v0.0.0-20181203201558-b27bcdd66705 h1:AKgLEI0V3PxcKmY9zZx5+Kdd3ahvIIsgkFbqc1r11J8= +github.com/moov-io/base v0.0.0-20181203201558-b27bcdd66705/go.mod h1:pPu/TAc9PkaaegbREVEeDHsGqyAlvji9vqTuARuAnd0= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.1 h1:K47Rk0v/fkEfwfQet2KWhscE0cJzjgCCDBG2KHZoVno= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= @@ -43,10 +41,11 @@ github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jO github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181129180645-aa55a523dc0a h1:Z2GBQ7wAiTCixJhSGK4sMO/FHYlvFvUBBK0M0FSsxeU= -github.com/prometheus/procfs v0.0.0-20181129180645-aa55a523dc0a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= diff --git a/http.go b/http.go index db1d4d978..7ac3c9242 100644 --- a/http.go +++ b/http.go @@ -5,15 +5,16 @@ package main import ( - "encoding/json" "errors" "fmt" "io" "io/ioutil" "net/http" + "strings" "time" - "github.com/moov-io/paygate/pkg/idempotent" + moovhttp "github.com/moov-io/base/http" + "github.com/moov-io/base/idempotent/lru" "github.com/go-kit/kit/log" "github.com/go-kit/kit/metrics" @@ -31,6 +32,8 @@ const ( ) var ( + inmemIdempot = lru.New() + errNoUserId = errors.New("no X-User-Id header provided") errMissingRequiredJson = errors.New("missing required JSON field(s)") @@ -53,64 +56,20 @@ func read(r io.Reader) ([]byte, error) { return ioutil.ReadAll(r) } -// getUserId grabs the userId from the http header, which is -// trusted. (The infra ensures this) -func getUserId(r *http.Request) string { - return r.Header.Get("X-User-Id") -} - -type idempot struct { - rec idempotent.Recorder -} - -// getIdempotencyKey extracts X-Idempotency-Key from the http request -// and checks if that key has been seen before. -func (i *idempot) getIdempotencyKey(r *http.Request) (key string, seen bool) { - key = r.Header.Get("X-Idempotency-Key") - if key == "" { - key = nextID() - } - return key, i.rec.SeenBefore(key) -} - -func idempotencyKeySeenBefore(w http.ResponseWriter) { - w.WriteHeader(http.StatusPreconditionFailed) -} - -// getRequestId extracts X-Request-Id from the http request, which -// is used in tracing requests. -// -// TODO(adam): IIRC a "max header size" param in net/http.Server - verify and configure -func getRequestId(r *http.Request) string { - return r.Header.Get("X-Request-Id") -} +func internalError(w http.ResponseWriter, err error) { + internalServerErrors.Add(1) -// encodeError JSON encodes the supplied error -// -// The HTTP status of "400 Bad Request" is written to the -// response. -func encodeError(w http.ResponseWriter, err error) { - if err == nil { - return - } - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(map[string]interface{}{ - "error": err.Error(), - }) -} + file := moovhttp.InternalError(w, err) + component := strings.Split(file, ".go")[0] -func internalError(w http.ResponseWriter, err error, component string) { - internalServerErrors.Add(1) if logger != nil { - logger.Log(component, err) + logger.Log(component, err, "source", file) } - w.WriteHeader(http.StatusInternalServerError) } func addPingRoute(r *mux.Router) { r.Methods("GET").Path("/ping").HandlerFunc(promhttp.InstrumentHandlerDuration(pingResponseDuration, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestId, userId := getRequestId(r), getUserId(r) + requestId, userId := moovhttp.GetRequestId(r), moovhttp.GetUserId(r) if requestId != "" { if userId == "" { userId = "" @@ -175,7 +134,7 @@ type paygateResponseWriter struct { // only execute once. Clients are assumed to resend requests many times // with the same key. We just need to reply back "already done". func (w *paygateResponseWriter) ensureHeaders(r *http.Request) error { - if v := getUserId(r); v == "" { + if v := moovhttp.GetUserId(r); v == "" { if !w.headersWritten { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusForbidden) @@ -184,7 +143,7 @@ func (w *paygateResponseWriter) ensureHeaders(r *http.Request) error { } else { w.userId = v } - w.requestId = getRequestId(r) + w.requestId = moovhttp.GetRequestId(r) // TODO(adam): idempotency check with an inmem bloom filter? // https://github.com/steakknife/bloomfilter diff --git a/http_test.go b/http_test.go index 5582c631b..bfdfa6499 100644 --- a/http_test.go +++ b/http_test.go @@ -5,7 +5,6 @@ package main import ( - "encoding/json" "errors" "net/http" "net/http/httptest" @@ -14,35 +13,9 @@ import ( "github.com/gorilla/mux" ) -func TestHttp__encodeError(t *testing.T) { - w := httptest.NewRecorder() - err := errors.New("test") - encodeError(w, err) - - w.Flush() - if w.Code != http.StatusBadRequest { - t.Errorf("got %d", w.Code) - } - if v := w.Header().Get("content-type"); v != "application/json; charset=utf-8" { - t.Errorf("got %s", v) - } - - // error response - type Err struct { - Error string `json:"error"` - } - var msg Err - if err := json.NewDecoder(w.Body).Decode(&msg); err != nil { - t.Error(err) - } - if msg.Error != "test" { - t.Error(msg.Error) - } -} - func TestHttp__internalError(t *testing.T) { w := httptest.NewRecorder() - internalError(w, errors.New("test"), "http_test") + internalError(w, errors.New("test")) w.Flush() if w.Code != http.StatusInternalServerError { diff --git a/main.go b/main.go index 3320c95d7..d8d5f255c 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,6 @@ import ( "github.com/moov-io/base/http/bind" "github.com/moov-io/paygate/internal/version" "github.com/moov-io/paygate/pkg/achclient" - "github.com/moov-io/paygate/pkg/idempotent/lru" "github.com/go-kit/kit/log" "github.com/go-kit/kit/metrics/prometheus" @@ -96,11 +95,6 @@ func main() { logger.Log("ach", "Pong successful to ACH service") } - // Setup X-Idempotency-Key manager - idempot := &idempot{ - rec: lru.New(), - } - // Create HTTP handler handler := mux.NewRouter() addCustomerRoutes(handler, customerRepo, depositoryRepo) @@ -109,7 +103,7 @@ func main() { addGatewayRoutes(handler, gatewaysRepo) addOriginatorRoutes(handler, depositoryRepo, originatorsRepo) addPingRoute(handler) - addTransfersRoute(handler, idempot, customerRepo, depositoryRepo, eventRepo, originatorsRepo, transferRepo) + addTransfersRoute(handler, customerRepo, depositoryRepo, eventRepo, originatorsRepo, transferRepo) // Listen for application termination. errs := make(chan error) diff --git a/microDeposits.go b/microDeposits.go index 33ebbd462..d9a8f6351 100644 --- a/microDeposits.go +++ b/microDeposits.go @@ -10,6 +10,8 @@ import ( "fmt" "net/http" "time" + + moovhttp "github.com/moov-io/base/http" ) var ( @@ -29,7 +31,7 @@ func initiateMicroDeposits(repo depositoryRepository) http.HandlerFunc { return } - id, userId := getDepositoryId(r), getUserId(r) + id, userId := getDepositoryId(r), moovhttp.GetUserId(r) if id == "" { // 404 - A depository with the specified ID was not found. w.WriteHeader(http.StatusNotFound) @@ -41,7 +43,7 @@ func initiateMicroDeposits(repo depositoryRepository) http.HandlerFunc { // Write micro deposits into our db if err := repo.initiateMicroDeposits(id, userId, fixedMicroDepositAmounts); err != nil { - internalError(w, err, "initiateMicroDeposits") + internalError(w, err) return } @@ -65,7 +67,7 @@ func confirmMicroDeposits(repo depositoryRepository) http.HandlerFunc { return } - id, userId := getDepositoryId(r), getUserId(r) + id, userId := getDepositoryId(r), moovhttp.GetUserId(r) if id == "" { // 404 - A depository with the specified ID was not found. w.WriteHeader(http.StatusNotFound) @@ -78,7 +80,7 @@ func confirmMicroDeposits(repo depositoryRepository) http.HandlerFunc { // Read amounts from request JSON var req confirmDepositoryRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } @@ -96,13 +98,13 @@ func confirmMicroDeposits(repo depositoryRepository) http.HandlerFunc { return } if err := repo.confirmMicroDeposits(id, userId, amounts); err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } // Update Depository status if err := markDepositoryVerified(repo, id, userId); err != nil { - internalError(w, err, "confirmMicroDeposits") + internalError(w, err) return } diff --git a/originators.go b/originators.go index 1247375ea..3182975a5 100644 --- a/originators.go +++ b/originators.go @@ -12,6 +12,8 @@ import ( "net/http" "time" + moovhttp "github.com/moov-io/base/http" + "github.com/go-kit/kit/log" "github.com/gorilla/mux" ) @@ -80,10 +82,10 @@ func getUserOriginators(originatorRepo originatorRepository) http.HandlerFunc { return } - userId := getUserId(r) + userId := moovhttp.GetUserId(r) origs, err := originatorRepo.getUserOriginators(userId) if err != nil { - internalError(w, err, "getUserOriginators") + internalError(w, err) return } @@ -91,7 +93,7 @@ func getUserOriginators(originatorRepo originatorRepository) http.HandlerFunc { w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(origs); err != nil { - internalError(w, err, "getUserOriginators") + internalError(w, err) return } } @@ -121,20 +123,20 @@ func createUserOriginator(originatorRepo originatorRepository, depositoryRepo de req, err := readOriginatorRequest(r) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } - userId := getUserId(r) + userId := moovhttp.GetUserId(r) if !depositoryIdExists(userId, req.DefaultDepository, depositoryRepo) { - encodeError(w, fmt.Errorf("Depository %s does not exist", req.DefaultDepository)) + moovhttp.Problem(w, fmt.Errorf("Depository %s does not exist", req.DefaultDepository)) return } orig, err := originatorRepo.createUserOriginator(userId, req) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } @@ -142,7 +144,7 @@ func createUserOriginator(originatorRepo originatorRepository, depositoryRepo de w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(orig); err != nil { - internalError(w, err, "createUserOriginator") + internalError(w, err) return } } @@ -155,10 +157,10 @@ func getUserOriginator(originatorRepo originatorRepository) http.HandlerFunc { return } - id, userId := getOriginatorId(r), getUserId(r) + id, userId := getOriginatorId(r), moovhttp.GetUserId(r) orig, err := originatorRepo.getUserOriginator(id, userId) if err != nil { - internalError(w, err, "getUserOriginator") + internalError(w, err) return } @@ -166,7 +168,7 @@ func getUserOriginator(originatorRepo originatorRepository) http.HandlerFunc { w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(orig); err != nil { - internalError(w, err, "getUserOriginator") + internalError(w, err) return } } @@ -179,9 +181,9 @@ func deleteUserOriginator(originatorRepo originatorRepository) http.HandlerFunc return } - id, userId := getOriginatorId(r), getUserId(r) + id, userId := getOriginatorId(r), moovhttp.GetUserId(r) if err := originatorRepo.deleteUserOriginator(id, userId); err != nil { - internalError(w, err, "deleteUserOriginator") + internalError(w, err) return } diff --git a/pkg/idempotent/idempotent.go b/pkg/idempotent/idempotent.go deleted file mode 100644 index e0da6d807..000000000 --- a/pkg/idempotent/idempotent.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2018 The Moov Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. - -package idempotent - -// Recorder offers a method to determine if a given key has been -// seen before or not. Each invocation of SeenBefore needs to -// record each key found, but there's no minimum duration required. -type Recorder interface { - SeenBefore(key string) bool -} diff --git a/pkg/idempotent/lru/lru_test.go b/pkg/idempotent/lru/lru_test.go deleted file mode 100644 index a36b16b05..000000000 --- a/pkg/idempotent/lru/lru_test.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018 The Moov Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. - -package lru - -import ( - "testing" -) - -func TestLRU(t *testing.T) { - cache := New() - - if cache.SeenBefore("key") { - t.Errorf("expected not seen") - } - - if !cache.SeenBefore("key") { - t.Errorf("expected seen") - } - - if cache.SeenBefore("other key") { - t.Errorf("expected not seen") - } -} diff --git a/transfers.go b/transfers.go index caf675a54..e19c9c8a1 100644 --- a/transfers.go +++ b/transfers.go @@ -15,6 +15,8 @@ import ( "unicode/utf8" "github.com/moov-io/ach" + moovhttp "github.com/moov-io/base/http" + "github.com/moov-io/base/idempotent" "github.com/moov-io/paygate/pkg/achclient" "github.com/go-kit/kit/log" @@ -193,12 +195,12 @@ type WEBPaymentType string // WEBReoccurring WEBPaymentType = "Reoccurring" // ) -func addTransfersRoute(r *mux.Router, idempot *idempot, custRepo customerRepository, depRepo depositoryRepository, eventRepo eventRepository, origRepo originatorRepository, transferRepo transferRepository) { +func addTransfersRoute(r *mux.Router, custRepo customerRepository, depRepo depositoryRepository, eventRepo eventRepository, origRepo originatorRepository, transferRepo transferRepository) { r.Methods("GET").Path("/transfers").HandlerFunc(getUserTransfers(transferRepo)) r.Methods("GET").Path("/transfers/{transferId}").HandlerFunc(getUserTransfer(transferRepo)) - r.Methods("POST").Path("/transfers").HandlerFunc(createUserTransfers(idempot, custRepo, depRepo, eventRepo, origRepo, transferRepo)) - r.Methods("POST").Path("/transfers/batch").HandlerFunc(createUserTransfers(idempot, custRepo, depRepo, eventRepo, origRepo, transferRepo)) + r.Methods("POST").Path("/transfers").HandlerFunc(createUserTransfers(custRepo, depRepo, eventRepo, origRepo, transferRepo)) + r.Methods("POST").Path("/transfers/batch").HandlerFunc(createUserTransfers(custRepo, depRepo, eventRepo, origRepo, transferRepo)) r.Methods("DELETE").Path("/transfers/{transferId}").HandlerFunc(deleteUserTransfer(transferRepo)) @@ -223,11 +225,11 @@ func getUserTransfers(transferRepo transferRepository) http.HandlerFunc { return } - userId := getUserId(r) + userId := moovhttp.GetUserId(r) transfers, err := transferRepo.getUserTransfers(userId) if err != nil { fmt.Println("A") - internalError(w, err, "getUserTransfers") + internalError(w, err) return } @@ -236,7 +238,7 @@ func getUserTransfers(transferRepo transferRepository) http.HandlerFunc { if err := json.NewEncoder(w).Encode(transfers); err != nil { fmt.Println("B") - internalError(w, err, "getUserTransfers") + internalError(w, err) return } } @@ -249,10 +251,10 @@ func getUserTransfer(transferRepo transferRepository) http.HandlerFunc { return } - id, userId := getTransferId(r), getUserId(r) + id, userId := getTransferId(r), moovhttp.GetUserId(r) transfer, err := transferRepo.getUserTransfer(id, userId) if err != nil { - internalError(w, err, "getUserTransfer") + internalError(w, err) return } @@ -260,7 +262,7 @@ func getUserTransfer(transferRepo transferRepository) http.HandlerFunc { w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(transfer); err != nil { - internalError(w, err, "getUserTransfer") + internalError(w, err) return } } @@ -293,34 +295,33 @@ func readTransferRequests(r *http.Request) ([]transferRequest, error) { return requests, nil } -func createUserTransfers(idempot *idempot, custRepo customerRepository, depRepo depositoryRepository, eventRepo eventRepository, origRepo originatorRepository, transferRepo transferRepository) http.HandlerFunc { +func createUserTransfers(custRepo customerRepository, depRepo depositoryRepository, eventRepo eventRepository, origRepo originatorRepository, transferRepo transferRepository) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w, err := wrapResponseWriter(w, r, "createUserTransfers") if err != nil { return } - // reject this request if we've seen it already - idempotencyKey, seen := idempot.getIdempotencyKey(r) + idempotencyKey, seen := idempotent.FromRequest(r, inmemIdempot) if seen { - idempotencyKeySeenBefore(w) + idempotent.SeenBefore(w) return } requests, err := readTransferRequests(r) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } - id, userId, requestId := nextID(), getUserId(r), getRequestId(r) + id, userId, requestId := nextID(), moovhttp.GetUserId(r), moovhttp.GetRequestId(r) ach := achclient.New(userId, logger) for i := range requests { req := requests[i] if err := req.missingFields(); err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } @@ -336,7 +337,7 @@ func createUserTransfers(idempot *idempot, custRepo customerRepository, depRepo logger.Log("transfers", fmt.Sprintf("Unable to find all objects during transfer create for user_id=%s, %s", userId, objects)) // Respond back to user - encodeError(w, fmt.Errorf("Missing data to create transfer: %s", err)) + moovhttp.Problem(w, fmt.Errorf("Missing data to create transfer: %s", err)) return } @@ -359,11 +360,11 @@ func createUserTransfers(idempot *idempot, custRepo customerRepository, depRepo fileId, err := createACHFile(ach, id, idempotencyKey, userId, transfer, cust, custDep, orig, origDep) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } if err := checkACHFile(ach, fileId, userId); err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } @@ -376,14 +377,14 @@ func createUserTransfers(idempot *idempot, custRepo customerRepository, depRepo Type: TransferEvent, }) if err != nil { - internalError(w, err, "transfers") + internalError(w, err) return } } transfers, err := transferRepo.createUserTransfers(userId, requests) if err != nil { - internalError(w, err, "transfers") + internalError(w, err) return } @@ -394,12 +395,12 @@ func createUserTransfers(idempot *idempot, custRepo customerRepository, depRepo // don't render surrounding array for single transfer create // (it's coming from POST /transfers, not POST /transfers/batch) if err := json.NewEncoder(w).Encode(transfers[0]); err != nil { - internalError(w, err, "createUserTransfers") + internalError(w, err) return } } else { if err := json.NewEncoder(w).Encode(transfers); err != nil { - internalError(w, err, "createUserTransfers") + internalError(w, err) return } } @@ -415,9 +416,9 @@ func deleteUserTransfer(transferRepo transferRepository) http.HandlerFunc { return } - id, userId := getTransferId(r), getUserId(r) + id, userId := getTransferId(r), moovhttp.GetUserId(r) if err := transferRepo.deleteUserTransfer(id, userId); err != nil { - internalError(w, err, "deleteUserTransfer") + internalError(w, err) return } @@ -460,17 +461,17 @@ func getUserTransferEvents(eventRepo eventRepository, transferRepo transferRepos return } - id, userId := getTransferId(r), getUserId(r) + id, userId := getTransferId(r), moovhttp.GetUserId(r) transfer, err := transferRepo.getUserTransfer(id, userId) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } events, err := eventRepo.getUserTransferEvents(userId, transfer.ID) if err != nil { - encodeError(w, err) + moovhttp.Problem(w, err) return } @@ -478,7 +479,7 @@ func getUserTransferEvents(eventRepo eventRepository, transferRepo transferRepos w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(events); err != nil { - internalError(w, err, "events") + internalError(w, err) return } diff --git a/transfers_test.go b/transfers_test.go index 6819185d0..e0128ec55 100644 --- a/transfers_test.go +++ b/transfers_test.go @@ -13,8 +13,6 @@ import ( "net/http/httptest" "testing" - "github.com/moov-io/paygate/pkg/idempotent/lru" - "github.com/go-kit/kit/log" "github.com/gorilla/mux" ) @@ -128,13 +126,9 @@ func TestTransfers__read(t *testing.T) { } func TestTransfers__idempotency(t *testing.T) { - idempot := &idempot{ - rec: lru.New(), - } - r := mux.NewRouter() // The repositories aren't used, aka idempotency check needs to be first. - addTransfersRoute(r, idempot, nil, nil, nil, nil, nil) + addTransfersRoute(r, nil, nil, nil, nil, nil) server := httptest.NewServer(r) client := server.Client() @@ -144,7 +138,7 @@ func TestTransfers__idempotency(t *testing.T) { req.Header.Set("X-User-Id", "user") // mark the key as seen - idempot.rec.SeenBefore("key") + inmemIdempot.SeenBefore("key") // make our request resp, err := client.Do(req) diff --git a/vendor/github.com/moov-io/base/http/doc.go b/vendor/github.com/moov-io/base/http/doc.go new file mode 100644 index 000000000..5a1ae4de8 --- /dev/null +++ b/vendor/github.com/moov-io/base/http/doc.go @@ -0,0 +1,12 @@ +// Copyright 2018 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +// Package http implements a core suite of HTTP functions for use inside Moov. These packages are designed to +// be used in production to provide insight without an excessive performance tradeoff. +// +// This package implements several opininated response functions (See Problem, InternalError) and stateless CORS +// handling under our load balancing setup. They may not work for you. +// +// This package also implements a wrapper around http.ResponseWriter to log X-Request-ID, timing and the resulting status code. +package http diff --git a/vendor/github.com/moov-io/base/http/response.go b/vendor/github.com/moov-io/base/http/response.go new file mode 100644 index 000000000..35b83d7a5 --- /dev/null +++ b/vendor/github.com/moov-io/base/http/response.go @@ -0,0 +1,110 @@ +// Copyright 2018 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package http + +import ( + "errors" + "fmt" + "net/http" + "strings" + "time" + + "github.com/moov-io/base/idempotent" + + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/metrics" +) + +var ( + ErrNoUserId = errors.New("no X-User-Id header provided") +) + +type ResponseWriter struct { + http.ResponseWriter + + start time.Time + request *http.Request + metric metrics.Histogram + + headersWritten bool // set on WriteHeader + + log log.Logger +} + +func (w *ResponseWriter) WriteHeader(code int) { + if w.headersWritten { + return + } + w.headersWritten = true + + // Headers + SetAccessControlAllowHeaders(w, w.request.Header.Get("Origin")) + defer w.ResponseWriter.WriteHeader(code) + + // Record route timing + diff := time.Since(w.start) + if w.metric != nil { + w.metric.Observe(diff.Seconds()) + } + + // Skip Go's content sniff here to speed up response timing for client + if w.ResponseWriter.Header().Get("Content-Type") == "" { + w.ResponseWriter.Header().Set("Content-Type", "text/plain") + w.ResponseWriter.Header().Set("X-Content-Type-Options", "nosniff") + } + + if requestId := GetRequestId(w.request); requestId != "" && w.log != nil { + var buf strings.Builder + buf.WriteString(fmt.Sprintf("method=%s ", w.request.Method)) + buf.WriteString(fmt.Sprintf("path=%s ", w.request.URL.Path)) + buf.WriteString(fmt.Sprintf("status=%d ", code)) + buf.WriteString(fmt.Sprintf("took=%s ", diff)) + buf.WriteString(fmt.Sprintf("requestId=%s ", requestId)) + + w.log.Log(w.request.Method, buf.String()) + } +} + +// Wrap returns a ResponseWriter usable by applications. No parts of the request are inspected. +func Wrap(logger log.Logger, m metrics.Histogram, w http.ResponseWriter, r *http.Request) *ResponseWriter { + now := time.Now() + return &ResponseWriter{ + ResponseWriter: w, + start: now, + request: r, + metric: m, + log: logger, + } +} + +// EnsureHeaders wraps the http.ResponseWriter but also checks Moov specific headers. +// +// X-User-Id is required, and requests without one will be completed with a 403 forbidden. +// No lookup is done to ensure the value exists and is valid for a Moov user. +// +// X-Request-Id is optional, but if used we will emit a log line with that request fulfillment timing +// and the status code. +// +// X-Idempotency-Key is optional, but recommended to ensure requests only execute once. Clients are +// assumed to resend requests many times with the same key. We just need to reply back "already done". +func EnsureHeaders(logger log.Logger, m metrics.Histogram, rec idempotent.Recorder, w http.ResponseWriter, r *http.Request) (*ResponseWriter, error) { + writer := Wrap(logger, m, w, r) + return writer, writer.ensureHeaders(rec) +} + +// ensureHeaders verifies the headers which Moov apps all cares about. +func (w *ResponseWriter) ensureHeaders(rec idempotent.Recorder) error { + if userId := GetUserId(w.request); userId == "" { + if !w.headersWritten { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusForbidden) + } + return ErrNoUserId + } + if _, seen := idempotent.FromRequest(w.request, rec); seen { + return idempotent.ErrSeenBefore + } + return nil +} diff --git a/vendor/github.com/moov-io/base/http/server.go b/vendor/github.com/moov-io/base/http/server.go new file mode 100644 index 000000000..abab62600 --- /dev/null +++ b/vendor/github.com/moov-io/base/http/server.go @@ -0,0 +1,121 @@ +// Copyright 2018 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package http + +import ( + "encoding/json" + "fmt" + "net/http" + "path/filepath" + "runtime" + "strings" + "unicode/utf8" + + "github.com/gorilla/mux" +) + +const ( + maxHeaderLength = 36 +) + +// Problem writes err to w while also setting the HTTP status code, content-type and marshaling +// err as the response body. +func Problem(w http.ResponseWriter, err error) { + if err == nil { + return + } + w.WriteHeader(http.StatusBadRequest) + w.Header().Set("Content-Type", "application/json; charset=utf-8") + json.NewEncoder(w).Encode(map[string]interface{}{ + "error": err.Error(), + }) +} + +// Problem writes err to w while also setting the HTTP status code, content-type and marshaling +// err as the response body. +// +// Returned is the calling file and line number: server.go:33 +func InternalError(w http.ResponseWriter, err error) string { + w.WriteHeader(http.StatusInternalServerError) + + pcs := make([]uintptr, 5) // some limit + _ = runtime.Callers(1, pcs) + + file, line := "", 0 + + // Sometimes InternalError will be wrapped by helper methods inside an application. + // We should linear search our callers until we find one outside github.com/moov-io + // because that likely represents the stdlib. + // + // Note: This might not work for code already outside github.com/moov-io, please report + // feedback if this works or not. + i, frames := 0, runtime.CallersFrames(pcs) + for { + f, more := frames.Next() + if !more { + break + } + + // f.Function can either be an absolute path (/Users/...) or a package + // (i.e. github.com/moov-io/...) so check for either. + if strings.Contains(f.Function, "github.com/moov-io") || strings.HasPrefix(f.Function, "main.") { + _, file, line, _ = runtime.Caller(i) // next caller + } + i++ + } + + // Get the filename, file was a full path + _, file = filepath.Split(file) + return fmt.Sprintf("%s:%d", file, line) +} + +// AddCORSHandler captures Corss Origin Resource Sharing (CORS) requests +// by looking at all OPTIONS requests for the Origin header, parsing that +// and responding back with the other Access-Control-Allow-* headers. +// +// Docs: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS +func AddCORSHandler(r *mux.Router) { + r.Methods("OPTIONS").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin := r.Header.Get("Origin") + if origin == "" { + w.WriteHeader(http.StatusBadRequest) + return + } + SetAccessControlAllowHeaders(w, r.Header.Get("Origin")) + w.WriteHeader(http.StatusOK) + }) +} + +// SetAccessControlAllowHeaders writes Access-Control-Allow-* headers to a response to allow +// for further CORS-allowed requests. +func SetAccessControlAllowHeaders(w http.ResponseWriter, origin string) { + // Access-Control-Allow-Origin can't be '*' with requests that send credentials. + // Instead, we need to explicitly set the domain (from request's Origin header) + // + // Allow requests from anyone's localhost and only from secure pages. + if strings.HasPrefix(origin, "http://localhost:") || strings.HasPrefix(origin, "https://") { + w.Header().Set("Access-Control-Allow-Origin", origin) + w.Header().Set("Access-Control-Allow-Methods", "GET,POST,PATCH,DELETE") + w.Header().Set("Access-Control-Allow-Headers", "Cookie,X-User-Id,X-Request-Id,Content-Type") + w.Header().Set("Access-Control-Allow-Credentials", "true") + } +} + +// GetRequestId returns the Moov header value for request IDs +func GetRequestId(r *http.Request) string { + return r.Header.Get("X-Request-Id") +} + +// GetUserId returns the Moov userId from HTTP headers +func GetUserId(r *http.Request) string { + return r.Header.Get("X-User-Id") +} + +func truncate(s string) string { + if utf8.RuneCountInString(s) > maxHeaderLength { + return s[:maxHeaderLength] + } + return s +} diff --git a/vendor/github.com/moov-io/base/idempotent/idempotent.go b/vendor/github.com/moov-io/base/idempotent/idempotent.go new file mode 100644 index 000000000..97cdf747b --- /dev/null +++ b/vendor/github.com/moov-io/base/idempotent/idempotent.go @@ -0,0 +1,54 @@ +// Copyright 2018 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package idempotent + +import ( + "errors" + "net/http" + "unicode/utf8" +) + +const ( + // maxIdempotencyKeyLength is the longest X-Idempotency-Key string legnth allowed. + maxIdempotencyKeyLength = 36 +) + +var ( + ErrSeenBefore = errors.New("X-Idempotency-Key seen before") +) + +// Recorder offers a method to determine if a given key has been +// seen before or not. Each invocation of SeenBefore needs to +// record each key found, but there's no minimum duration required. +type Recorder interface { + SeenBefore(key string) bool +} + +// FromRequest extracts the idempotency key from HTTP headers and records its presence in +// the provided Recorder. +// +// A nil Recorder will always return idempotency keys as unseen. +func FromRequest(req *http.Request, rec Recorder) (key string, seen bool) { + key = truncate(req.Header.Get("X-Idempotency-Key")) + if rec == nil { + return key, false + } + if key == "" { + return "", false + } + return key, rec.SeenBefore(key) +} + +// SeenBefore sets a HTTP response code as an error for previously seen idempotency keys. +func SeenBefore(w http.ResponseWriter) { + w.WriteHeader(http.StatusPreconditionFailed) +} + +func truncate(s string) string { + if utf8.RuneCountInString(s) > maxIdempotencyKeyLength { + return s[:maxIdempotencyKeyLength] + } + return s +} diff --git a/pkg/idempotent/lru/lru.go b/vendor/github.com/moov-io/base/idempotent/lru/lru.go similarity index 95% rename from pkg/idempotent/lru/lru.go rename to vendor/github.com/moov-io/base/idempotent/lru/lru.go index 2531fc864..96681e505 100644 --- a/pkg/idempotent/lru/lru.go +++ b/vendor/github.com/moov-io/base/idempotent/lru/lru.go @@ -30,6 +30,10 @@ type Mem struct { } func (m *Mem) SeenBefore(key string) bool { + if m == nil { + return false + } + seen := m.cache.Contains(key) if !seen { m.cache.Add(key, defaultValue) diff --git a/vendor/github.com/prometheus/client_golang/prometheus/desc.go b/vendor/github.com/prometheus/client_golang/prometheus/desc.go index 7b8827ffb..1d034f871 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/desc.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/desc.go @@ -93,7 +93,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) * // First add only the const label names and sort them... for labelName := range constLabels { if !checkLabelName(labelName) { - d.err = fmt.Errorf("%q is not a valid label name", labelName) + d.err = fmt.Errorf("%q is not a valid label name for metric %q", labelName, fqName) return d } labelNames = append(labelNames, labelName) @@ -115,7 +115,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) * // dimension with a different mix between preset and variable labels. for _, labelName := range variableLabels { if !checkLabelName(labelName) { - d.err = fmt.Errorf("%q is not a valid label name", labelName) + d.err = fmt.Errorf("%q is not a valid label name for metric %q", labelName, fqName) return d } labelNames = append(labelNames, "$"+labelName) diff --git a/vendor/github.com/prometheus/client_golang/prometheus/registry.go b/vendor/github.com/prometheus/client_golang/prometheus/registry.go index f98c81a86..b5e70b93f 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/registry.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/registry.go @@ -872,7 +872,13 @@ func checkMetricConsistency( h = hashAddByte(h, separatorByte) // Make sure label pairs are sorted. We depend on it for the consistency // check. - sort.Sort(labelPairSorter(dtoMetric.Label)) + if !sort.IsSorted(labelPairSorter(dtoMetric.Label)) { + // We cannot sort dtoMetric.Label in place as it is immutable by contract. + copiedLabels := make([]*dto.LabelPair, len(dtoMetric.Label)) + copy(copiedLabels, dtoMetric.Label) + sort.Sort(labelPairSorter(copiedLabels)) + dtoMetric.Label = copiedLabels + } for _, lp := range dtoMetric.Label { h = hashAdd(h, lp.GetName()) h = hashAddByte(h, separatorByte) @@ -903,8 +909,8 @@ func checkDescConsistency( } // Is the desc consistent with the content of the metric? - lpsFromDesc := make([]*dto.LabelPair, 0, len(dtoMetric.Label)) - lpsFromDesc = append(lpsFromDesc, desc.constLabelPairs...) + lpsFromDesc := make([]*dto.LabelPair, len(desc.constLabelPairs), len(dtoMetric.Label)) + copy(lpsFromDesc, desc.constLabelPairs) for _, l := range desc.variableLabels { lpsFromDesc = append(lpsFromDesc, &dto.LabelPair{ Name: proto.String(l), diff --git a/vendor/github.com/prometheus/client_golang/prometheus/timer.go b/vendor/github.com/prometheus/client_golang/prometheus/timer.go index b8fc5f18c..8d5f10523 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/timer.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/timer.go @@ -39,13 +39,16 @@ func NewTimer(o Observer) *Timer { // ObserveDuration records the duration passed since the Timer was created with // NewTimer. It calls the Observe method of the Observer provided during -// construction with the duration in seconds as an argument. ObserveDuration is -// usually called with a defer statement. +// construction with the duration in seconds as an argument. The observed +// duration is also returned. ObserveDuration is usually called with a defer +// statement. // // Note that this method is only guaranteed to never observe negative durations // if used with Go1.9+. -func (t *Timer) ObserveDuration() { +func (t *Timer) ObserveDuration() time.Duration { + d := time.Since(t.begin) if t.observer != nil { - t.observer.Observe(time.Since(t.begin).Seconds()) + t.observer.Observe(d.Seconds()) } + return d } diff --git a/vendor/github.com/prometheus/procfs/Makefile b/vendor/github.com/prometheus/procfs/Makefile index 40540b02e..947d7d8fa 100644 --- a/vendor/github.com/prometheus/procfs/Makefile +++ b/vendor/github.com/prometheus/procfs/Makefile @@ -23,5 +23,8 @@ update_fixtures: fixtures.ttar sysfs/fixtures.ttar rm -v $(dir $*)fixtures/.unpacked ./ttar -C $(dir $*) -c -f $*fixtures.ttar fixtures/ +.PHONY: build +build: + .PHONY: test test: fixtures/.unpacked sysfs/fixtures/.unpacked common-test diff --git a/vendor/modules.txt b/vendor/modules.txt index ccd29d4d6..067911a9f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -27,10 +27,13 @@ github.com/moov-io/ach github.com/moov-io/ach/internal/iso3166 github.com/moov-io/ach/internal/iso4217 github.com/moov-io/ach/internal/usabbrev -# github.com/moov-io/base v0.0.0-20181127010612-e1e22f6949b7 +# github.com/moov-io/base v0.0.0-20181203201558-b27bcdd66705 github.com/moov-io/base/admin +github.com/moov-io/base/http github.com/moov-io/base/http/bind -# github.com/prometheus/client_golang v0.9.1 +github.com/moov-io/base/idempotent +github.com/moov-io/base/idempotent/lru +# github.com/prometheus/client_golang v0.9.2 github.com/prometheus/client_golang/prometheus github.com/prometheus/client_golang/prometheus/promauto github.com/prometheus/client_golang/prometheus/promhttp @@ -41,7 +44,7 @@ github.com/prometheus/client_model/go github.com/prometheus/common/expfmt github.com/prometheus/common/model github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg -# github.com/prometheus/procfs v0.0.0-20181129180645-aa55a523dc0a +# github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a github.com/prometheus/procfs github.com/prometheus/procfs/nfs github.com/prometheus/procfs/xfs