Skip to content

Commit 6d0dbb4

Browse files
committed
refactor: middleware call stack
1 parent 0953290 commit 6d0dbb4

10 files changed

Lines changed: 111 additions & 49 deletions

File tree

barf.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ func createServer(a typing.Augment) error {
2929

3030
var r http.Handler = server.Mux
3131

32-
// wrap router into logger middleware
32+
// enable logging
3333
if *server.Augment.Logging {
34-
r = logger.Morgan(r)
34+
go logger.Winston()
3535
}
3636

3737
// wrap router into router middleware
@@ -60,10 +60,7 @@ func createServer(a typing.Augment) error {
6060
}
6161

6262
// this will load the CORS and Recovery middleware into the stack
63-
if *server.Augment.Recovery {
64-
Hippocampus().Hijack()
65-
logger.Info("Recovery middleware added to base barf handler")
66-
}
63+
Hippocampus().Hijack()
6764

6865
return nil
6966
}

example/subroute/main.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,27 @@ func main() {
5353
// create a subrouter (retroframe)
5454
var r *barf.SubRoute = barf.RetroFrame("/api")
5555

56-
// apply middleware to subrouter. note that the only difference between this and global middlewares is that you need to pass the
56+
// apply middleware to subrouter. note that the only difference between this and global middlewares is that you need to pass the subrouter to Hippocampus()
5757
barf.Hippocampus(r).Hijack(func(h http.Handler) http.Handler {
5858
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
5959
barf.Logger().Info("sub before 0")
60+
// barf.Response(w).Status(http.StatusNotFound).JSON(barf.Res{
61+
// Message: "Not found 0",
62+
// })
63+
// return
6064
h.ServeHTTP(w, r)
6165
barf.Logger().Info("sub after 0")
6266
})
6367
})
6468

69+
barf.Hippocampus(r).Hijack(func(h http.Handler) http.Handler {
70+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
71+
barf.Logger().Info("sub before 1")
72+
h.ServeHTTP(w, r)
73+
barf.Logger().Info("sub after 1")
74+
})
75+
})
76+
6577
r.Get("/home", func(w http.ResponseWriter, r *http.Request) {
6678
barf.Response(w).Status(http.StatusOK).JSON(barf.Res{
6779
Status: true,

log/middleware.go

Lines changed: 0 additions & 25 deletions
This file was deleted.

log/morgan.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package logger
2+
3+
import (
4+
"net/http"
5+
"strconv"
6+
"time"
7+
8+
"github.com/opensaucerer/barf/server"
9+
"github.com/opensaucerer/barf/typing"
10+
)
11+
12+
// Morgan logs the request to the console
13+
func Morgan(rr typing.RequestResponse) {
14+
15+
// format: utc timestamp: user-agent - http/version: method - path - status code - status text
16+
msg := time.Now().UTC().Format(time.RFC3339) + ": " + rr.Request.UserAgent() + " - " + rr.Request.Proto + ": " + rr.Request.Method + " - " + rr.Request.URL.Path + " - " + strconv.Itoa(rr.Code) + " - " + http.StatusText(rr.Code)
17+
// log request
18+
Code(msg, rr.Code)
19+
20+
// call next middleware? (the logger is the last middleware in the stack)
21+
// next.ServeHTTP(w, r)
22+
23+
}
24+
25+
// Winston listens on a channel for log messages and calls morgan for each message
26+
func Winston() {
27+
for msg := range server.RequestResponseChan {
28+
Morgan(msg)
29+
}
30+
}

router/hippocampus.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package router
22

33
import (
44
"github.com/opensaucerer/barf/cors"
5+
logger "github.com/opensaucerer/barf/log"
6+
57
"github.com/opensaucerer/barf/server"
68

79
"github.com/opensaucerer/barf/typing"
@@ -51,11 +53,19 @@ func (h *hippocampus) Hijack(m ...typing.Middleware) {
5153
for i := range h.stack {
5254
r = h.stack[len(h.stack)-1-i](r)
5355
}
54-
// add cors middleware such that it is called first before any user-defined middleware
55-
r = cors.CORS(cors.Prepare(*server.Augment.CORS))(r)
56-
// add recovery middleware
57-
if server.Augment.Recovery != nil && *server.Augment.Recovery {
58-
r = server.Recover(server.JSON)(r)
56+
57+
if !server.Hijacked {
58+
// add cors middleware such that it is called first before any user-defined middleware
59+
r = cors.CORS(cors.Prepare(*server.Augment.CORS))(r)
60+
// add recovery middleware
61+
if server.Augment.Recovery != nil && *server.Augment.Recovery {
62+
r = server.Recover(server.JSON)(r)
63+
logger.Info("Recovery middleware added to base barf handler")
64+
} else {
65+
logger.Warn("Recovery middleware is disabled. This is not recommended for production environments!")
66+
logger.Warn("Consider enabling it by passing barf.Augment{Recovery: barf.Allow()} to barf.Stark()")
67+
}
68+
server.Hijacked = true
5969
}
6070
server.HTTP.Handler = r
6171
}

router/middleware.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package router
33
import (
44
"context"
55
"fmt"
6+
"log"
67
"net/http"
78
"regexp"
89
"strings"
@@ -33,11 +34,19 @@ func Router(respond func(w http.ResponseWriter, status bool, statusCode int, mes
3334

3435
// check if route exists
3536
if !route.Exists() {
37+
log.Println("route not found", route.Path, route.Method)
3638
respond(w, false, http.StatusNotFound, fmt.Sprintf("Path /%s for method %s not found", regexp.MustCompile("^/+|/+$").ReplaceAllString(route.Path, ""), strings.ToUpper(route.Method)), nil)
3739
} else {
3840
// load params into context if any
3941
ctx := context.WithValue(r.Context(), typing.ParamsCtxKey{}, route.Params)
4042

43+
r = r.WithContext(ctx)
44+
45+
// call route specific middleware(s)
46+
for i := range route.stack {
47+
sh = route.stack[len(route.stack)-1-i](sh)
48+
}
49+
4150
// call middleware stack if route is registered on a SubRoute
4251
if route.RetroFrame {
4352
s := route.Reframe()
@@ -47,16 +56,15 @@ func Router(respond func(w http.ResponseWriter, status bool, statusCode int, mes
4756
}
4857
}
4958
}
59+
}
5060

51-
// call route specific middleware(s)
52-
for i := range route.stack {
53-
sh = route.stack[len(route.stack)-1-i](sh)
54-
}
55-
56-
// call route handler
57-
route.Handler(w, r.WithContext(ctx))
61+
// make request/response available globally
62+
server.RequestResponse = &typing.RequestResponse{
63+
Request: r,
64+
Response: w,
5865
}
59-
// call the next middleware
66+
67+
// call the next middleware (if no route was found)
6068
sh.ServeHTTP(w, r)
6169
})
6270
}

router/register.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package router
22

33
import (
4+
"net/http"
45
"regexp"
56
)
67

@@ -15,4 +16,11 @@ func (r *Route) Register() {
1516
rtable[r.Path] = make(map[string]*Route)
1617
}
1718
rtable[r.Path][r.Method] = r
19+
// the handler is added as just another middleware
20+
r.stack = append(r.stack, func(h http.Handler) http.Handler {
21+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
22+
r.Handler(w, req)
23+
h.ServeHTTP(w, req)
24+
})
25+
})
1826
}

server/http.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,10 @@ var (
1414
Augment *typing.Augment
1515

1616
Beckoned *bool
17+
18+
Hijacked bool
19+
20+
RequestResponse *typing.RequestResponse
21+
22+
RequestResponseChan = make(chan typing.RequestResponse)
1723
)

server/response.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ type response struct {
2424

2525
// JSON writes a JSON response to the response writer
2626
func (r *response) JSON(data interface{}) {
27-
r.rw.Header().Set(constant.ContentType, constant.ApplicationJSON)
28-
r.rw.WriteHeader(r.code)
29-
json.NewEncoder(r.rw).Encode(data)
30-
Write(r.rw) // write to underlying response writer
27+
if Loaded(r.rw) && !Written(r.rw) {
28+
r.rw.Header().Set(constant.ContentType, constant.ApplicationJSON)
29+
r.rw.WriteHeader(r.code)
30+
json.NewEncoder(r.rw).Encode(data)
31+
Write(r.rw) // write to underlying response writer
32+
}
3133
}
3234

3335
// Status loads a barf response with the given status code
@@ -110,6 +112,13 @@ func Write(w http.ResponseWriter) {
110112
}
111113
w.(*ResponseWriter).rw.WriteHeader(w.(*ResponseWriter).Code)
112114
w.(*ResponseWriter).rw.Write(w.(*ResponseWriter).Body)
115+
116+
// signal the logger middleware that the response has been written to
117+
RequestResponseChan <- typing.RequestResponse{
118+
Request: RequestResponse.Request,
119+
Response: w.(*ResponseWriter),
120+
Code: w.(*ResponseWriter).Code,
121+
}
113122
}
114123
}
115124

typing/barf.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,10 @@ type CORS struct {
8181
// AllowedOriginWithRequestFunc is a callback for handling user defined origin checks with access to the http request object.
8282
AllowedOriginWithRequestFunc func(origin string, r *http.Request) bool
8383
}
84+
85+
// RequestResponse holds the request and response objects
86+
type RequestResponse struct {
87+
Request *http.Request
88+
Response http.ResponseWriter
89+
Code int
90+
}

0 commit comments

Comments
 (0)