Use gorilla/mux to route HTTP requests (#11)

* Add basic routing based on matched paths
* Make /sync and /send use the right API paths
This commit is contained in:
Kegsay 2017-02-20 15:41:29 +00:00 committed by GitHub
parent 154110b7c2
commit f1bb59d24a
7 changed files with 131 additions and 43 deletions

2
vendor/manifest vendored
View file

@ -86,7 +86,7 @@
{
"importpath": "github.com/matrix-org/util",
"repository": "https://github.com/matrix-org/util",
"revision": "0f4d9cce82badc0741ff1141dcf079312cb4d2f0",
"revision": "0bbc3896e02031e7e7338948b73ce891aa73ab2b",
"branch": "master"
},
{

View file

@ -12,11 +12,33 @@ import (
log "github.com/Sirupsen/logrus"
)
// ContextKeys is a type alias for string to namespace Context keys per-package.
type ContextKeys string
// contextKeys is a type alias for string to namespace Context keys per-package.
type contextKeys string
// CtxValueLogger is the key to extract the logrus Logger.
const CtxValueLogger = ContextKeys("logger")
// ctxValueRequestID is the key to extract the request ID for an HTTP request
const ctxValueRequestID = contextKeys("requestid")
// GetRequestID returns the request ID associated with this context, or the empty string
// if one is not associated with this context.
func GetRequestID(ctx context.Context) string {
id := ctx.Value(ctxValueRequestID)
if id == nil {
return ""
}
return id.(string)
}
// ctxValueLogger is the key to extract the logrus Logger.
const ctxValueLogger = contextKeys("logger")
// GetLogger retrieves the logrus logger from the supplied context. Returns nil if there is no logger.
func GetLogger(ctx context.Context) *log.Entry {
l := ctx.Value(ctxValueLogger)
if l == nil {
return nil
}
return l.(*log.Entry)
}
// JSONRequestHandler represents an interface that must be satisfied in order to respond to incoming
// HTTP requests with JSON. The interface returned will be marshalled into JSON to be sent to the client,
@ -34,12 +56,12 @@ type JSONError struct {
// Protect panicking HTTP requests from taking down the entire process, and log them using
// the correct logger, returning a 500 with a JSON response rather than abruptly closing the
// connection. The http.Request MUST have a CtxValueLogger.
// connection. The http.Request MUST have a ctxValueLogger.
func Protect(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
defer func() {
if r := recover(); r != nil {
logger := req.Context().Value(CtxValueLogger).(*log.Entry)
logger := req.Context().Value(ctxValueLogger).(*log.Entry)
logger.WithFields(log.Fields{
"panic": r,
}).Errorf(
@ -56,18 +78,20 @@ func Protect(handler http.HandlerFunc) http.HandlerFunc {
// MakeJSONAPI creates an HTTP handler which always responds to incoming requests with JSON responses.
// Incoming http.Requests will have a logger (with a request ID/method/path logged) attached to the Context.
// This can be accessed via the const CtxValueLogger. The type of the logger is *log.Entry from github.com/Sirupsen/logrus
// This can be accessed via GetLogger(Context). The type of the logger is *log.Entry from github.com/Sirupsen/logrus
func MakeJSONAPI(handler JSONRequestHandler) http.HandlerFunc {
return Protect(func(w http.ResponseWriter, req *http.Request) {
reqID := RandomString(12)
// Set a Logger on the context
ctx := context.WithValue(req.Context(), CtxValueLogger, log.WithFields(log.Fields{
ctx := context.WithValue(req.Context(), ctxValueLogger, log.WithFields(log.Fields{
"req.method": req.Method,
"req.path": req.URL.Path,
"req.id": RandomString(12),
"req.id": reqID,
}))
ctx = context.WithValue(ctx, ctxValueRequestID, reqID)
req = req.WithContext(ctx)
logger := req.Context().Value(CtxValueLogger).(*log.Entry)
logger := req.Context().Value(ctxValueLogger).(*log.Entry)
logger.Print("Incoming request")
res, httpErr := handler.OnIncomingRequest(req)
@ -99,7 +123,7 @@ func MakeJSONAPI(handler JSONRequestHandler) http.HandlerFunc {
}
func jsonErrorResponse(w http.ResponseWriter, req *http.Request, httpErr *HTTPError) {
logger := req.Context().Value(CtxValueLogger).(*log.Entry)
logger := req.Context().Value(ctxValueLogger).(*log.Entry)
if httpErr.Code == 302 {
logger.WithField("err", httpErr.Error()).Print("Redirecting")
http.Redirect(w, req, httpErr.Message, 302)

View file

@ -73,12 +73,30 @@ func TestMakeJSONAPIRedirect(t *testing.T) {
}
}
func TestGetLogger(t *testing.T) {
log.SetLevel(log.PanicLevel) // suppress logs in test output
entry := log.WithField("test", "yep")
mockReq, _ := http.NewRequest("GET", "http://example.com/foo", nil)
ctx := context.WithValue(mockReq.Context(), ctxValueLogger, entry)
mockReq = mockReq.WithContext(ctx)
ctxLogger := GetLogger(mockReq.Context())
if ctxLogger != entry {
t.Errorf("TestGetLogger wanted logger '%v', got '%v'", entry, ctxLogger)
}
noLoggerInReq, _ := http.NewRequest("GET", "http://example.com/foo", nil)
ctxLogger = GetLogger(noLoggerInReq.Context())
if ctxLogger != nil {
t.Errorf("TestGetLogger wanted nil logger, got '%v'", ctxLogger)
}
}
func TestProtect(t *testing.T) {
log.SetLevel(log.PanicLevel) // suppress logs in test output
mockWriter := httptest.NewRecorder()
mockReq, _ := http.NewRequest("GET", "http://example.com/foo", nil)
mockReq = mockReq.WithContext(
context.WithValue(mockReq.Context(), CtxValueLogger, log.WithField("test", "yep")),
context.WithValue(mockReq.Context(), ctxValueLogger, log.WithField("test", "yep")),
)
h := Protect(func(w http.ResponseWriter, req *http.Request) {
panic("oh noes!")
@ -97,3 +115,21 @@ func TestProtect(t *testing.T) {
t.Errorf("TestProtect wanted body %s, got %s", expectBody, actualBody)
}
}
func TestGetRequestID(t *testing.T) {
log.SetLevel(log.PanicLevel) // suppress logs in test output
reqID := "alphabetsoup"
mockReq, _ := http.NewRequest("GET", "http://example.com/foo", nil)
ctx := context.WithValue(mockReq.Context(), ctxValueRequestID, reqID)
mockReq = mockReq.WithContext(ctx)
ctxReqID := GetRequestID(mockReq.Context())
if reqID != ctxReqID {
t.Errorf("TestGetRequestID wanted request ID '%s', got '%s'", reqID, ctxReqID)
}
noReqIDInReq, _ := http.NewRequest("GET", "http://example.com/foo", nil)
ctxReqID = GetRequestID(noReqIDInReq.Context())
if ctxReqID != "" {
t.Errorf("TestGetRequestID wanted empty request ID, got '%s'", ctxReqID)
}
}