Browse Source

Ported middlewares

Wijnand Modderman-Lenstra 2 years ago
parent
commit
224b54bd80

+ 2
- 2
README.md View File

@@ -48,8 +48,8 @@ Markdown ([routes.md](http://git.maze.io/go/phi/src/master/_examples/rest/routes
48 48
 import (
49 49
   //...
50 50
   "context"
51
-  "maze.io/phi"
52
-  "maze.io/phi/middleware"
51
+  phi "maze.io/phi.v1"
52
+  "maze.io/phi.v1/middleware"
53 53
 )
54 54
 
55 55
 func main() {

+ 2
- 2
chain.go View File

@@ -1,7 +1,7 @@
1 1
 package phi
2 2
 
3 3
 // Chain returns a Middlewares type from a slice of middleware handlers.
4
-func Chain(middlewares ...func(Handler) Handler) Middlewares {
4
+func Chain(middlewares ...Middleware) Middlewares {
5 5
 	return Middlewares(middlewares)
6 6
 }
7 7
 
@@ -31,7 +31,7 @@ func (c *ChainHandler) Serve(ctx Context) {
31 31
 
32 32
 // chain builds a http.Handler composed of an inline middleware stack and endpoint
33 33
 // handler in the order they are passed.
34
-func chain(middlewares []func(Handler) Handler, endpoint Handler) Handler {
34
+func chain(middlewares []Middleware, endpoint Handler) Handler {
35 35
 	// Return ahead of time if there aren't any middlewares for the chain
36 36
 	if middlewares == nil || len(middlewares) == 0 {
37 37
 		return endpoint

+ 29
- 0
context.go View File

@@ -12,11 +12,24 @@ var (
12 12
 
13 13
 // Context is the generic context interface.
14 14
 type Context interface {
15
+	// ResponseWriter interface
15 16
 	http.ResponseWriter
16 17
 
18
+	// RoutePath is the routing path override used by sub routers in the context.
19
+	RoutePath() string
20
+
21
+	// SetRoutePath updates the route path.
22
+	SetRoutePath(string)
23
+
24
+	// SetResponseWriter updates the HTTP response writer.
25
+	SetResponseWriter(http.ResponseWriter)
26
+
17 27
 	// Request is the orignal HTTP request.
18 28
 	Request() *http.Request
19 29
 
30
+	// SetRequest updates the HTTP request.
31
+	SetRequest(*http.Request)
32
+
20 33
 	// Context cancellation.
21 34
 	Done() <-chan struct{}
22 35
 
@@ -80,10 +93,26 @@ func (x *RoutingContext) reset(w http.ResponseWriter, r *http.Request) {
80 93
 	x.data = make(map[string]interface{})
81 94
 }
82 95
 
96
+func (x *RoutingContext) RoutePath() string {
97
+	return x.routePath
98
+}
99
+
100
+func (x *RoutingContext) SetRoutePath(routePath string) {
101
+	x.routePath = routePath
102
+}
103
+
104
+func (x *RoutingContext) SetResponseWriter(w http.ResponseWriter) {
105
+	x.ResponseWriter = w
106
+}
107
+
83 108
 func (x *RoutingContext) Request() *http.Request {
84 109
 	return x.request
85 110
 }
86 111
 
112
+func (x *RoutingContext) SetRequest(r *http.Request) {
113
+	x.request = r
114
+}
115
+
87 116
 func (x *RoutingContext) Data() map[string]interface{} {
88 117
 	return x.data
89 118
 }

+ 1
- 1
docgen/builder.go View File

@@ -7,7 +7,7 @@ import (
7 7
 	"path/filepath"
8 8
 	"strings"
9 9
 
10
-	"maze.io/phi"
10
+	phi "maze.io/phi.v1"
11 11
 )
12 12
 
13 13
 func BuildDoc(r phi.Routes) (Doc, error) {

+ 2
- 1
docgen/docgen.go View File

@@ -1,10 +1,11 @@
1
+// Package docgen provides functions for documenting phi routes.
1 2
 package docgen
2 3
 
3 4
 import (
4 5
 	"encoding/json"
5 6
 	"fmt"
6 7
 
7
-	"maze.io/phi"
8
+	phi "maze.io/phi.v1"
8 9
 )
9 10
 
10 11
 type Doc struct {

+ 24
- 24
docgen/docgen_test.go View File

@@ -4,19 +4,19 @@ import (
4 4
 	"fmt"
5 5
 	"testing"
6 6
 
7
-	"maze.io/phi"
8
-	"maze.io/phi/docgen"
7
+	phi "maze.io/phi.v1"
8
+	"maze.io/phi.v1/docgen"
9 9
 )
10 10
 
11 11
 // RequestID comment goes here.
12 12
 func RequestID(next phi.Handler) phi.Handler {
13
-	return phi.HandlerFunc(func(ctx *phi.Context) {
13
+	return phi.HandlerFunc(func(ctx phi.Context) {
14 14
 		ctx.SetValue("requestID", "1")
15 15
 		next.Serve(ctx)
16 16
 	})
17 17
 }
18 18
 
19
-func hubIndexHandler(ctx *phi.Context) {
19
+func hubIndexHandler(ctx phi.Context) {
20 20
 	s := fmt.Sprintf("/hubs/%s reqid:%s session:%s",
21 21
 		ctx.Param("hubID"), ctx.Value("requestID"), ctx.Value("session.user"))
22 22
 	ctx.Write([]byte(s))
@@ -31,26 +31,26 @@ func TestMuxBig(t *testing.T) {
31 31
 	// Some inline middleware, 1
32 32
 	// We just love Go's ast tools
33 33
 	r.Use(func(next phi.Handler) phi.Handler {
34
-		return phi.HandlerFunc(func(ctx *phi.Context) {
34
+		return phi.HandlerFunc(func(ctx phi.Context) {
35 35
 			next.Serve(ctx)
36 36
 		})
37 37
 	})
38 38
 	r.Group(func(r phi.Router) {
39 39
 		r.Use(func(next phi.Handler) phi.Handler {
40
-			return phi.HandlerFunc(func(ctx *phi.Context) {
40
+			return phi.HandlerFunc(func(ctx phi.Context) {
41 41
 				ctx.SetValue("session.user", "anonymous")
42 42
 				next.Serve(ctx)
43 43
 			})
44 44
 		})
45
-		r.Get("/favicon.ico", func(ctx *phi.Context) {
45
+		r.Get("/favicon.ico", func(ctx phi.Context) {
46 46
 			ctx.Write([]byte("fav"))
47 47
 		})
48
-		r.Get("/hubs/:hubID/view", func(ctx *phi.Context) {
48
+		r.Get("/hubs/:hubID/view", func(ctx phi.Context) {
49 49
 			s := fmt.Sprintf("/hubs/%s/view reqid:%s session:%s", ctx.Param("hubID"),
50 50
 				ctx.Value("requestID"), ctx.Value("session.user"))
51 51
 			ctx.Write([]byte(s))
52 52
 		})
53
-		r.Get("/hubs/:hubID/view/*", func(ctx *phi.Context) {
53
+		r.Get("/hubs/:hubID/view/*", func(ctx phi.Context) {
54 54
 			s := fmt.Sprintf("/hubs/%s/view/%s reqid:%s session:%s", ctx.Param("hubID"),
55 55
 				ctx.Param("*"), ctx.Value("requestID"), ctx.Value("session.user"))
56 56
 			ctx.Write([]byte(s))
@@ -58,21 +58,21 @@ func TestMuxBig(t *testing.T) {
58 58
 	})
59 59
 	r.Group(func(r phi.Router) {
60 60
 		r.Use(func(next phi.Handler) phi.Handler {
61
-			return phi.HandlerFunc(func(ctx *phi.Context) {
61
+			return phi.HandlerFunc(func(ctx phi.Context) {
62 62
 				ctx.SetValue("session.user", "elvis")
63 63
 				next.Serve(ctx)
64 64
 			})
65 65
 		})
66
-		r.Get("/", func(ctx *phi.Context) {
66
+		r.Get("/", func(ctx phi.Context) {
67 67
 			s := fmt.Sprintf("/ reqid:%s session:%s", ctx.Value("requestID"), ctx.Value("session.user"))
68 68
 			ctx.Write([]byte(s))
69 69
 		})
70
-		r.Get("/suggestions", func(ctx *phi.Context) {
70
+		r.Get("/suggestions", func(ctx phi.Context) {
71 71
 			s := fmt.Sprintf("/suggestions reqid:%s session:%s", ctx.Value("requestID"), ctx.Value("session.user"))
72 72
 			ctx.Write([]byte(s))
73 73
 		})
74 74
 
75
-		r.Get("/woot/:wootID/*", func(ctx *phi.Context) {
75
+		r.Get("/woot/:wootID/*", func(ctx phi.Context) {
76 76
 			s := fmt.Sprintf("/woot/%s/%s", ctx.Param("wootID"), ctx.Param("*"))
77 77
 			ctx.Write([]byte(s))
78 78
 		})
@@ -80,28 +80,28 @@ func TestMuxBig(t *testing.T) {
80 80
 		r.Route("/hubs", func(r phi.Router) {
81 81
 			sr1 = r.(*phi.Mux)
82 82
 			r.Use(func(next phi.Handler) phi.Handler {
83
-				return phi.HandlerFunc(func(ctx *phi.Context) {
83
+				return phi.HandlerFunc(func(ctx phi.Context) {
84 84
 					next.Serve(ctx)
85 85
 				})
86 86
 			})
87 87
 			r.Route("/:hubID", func(r phi.Router) {
88 88
 				sr2 = r.(*phi.Mux)
89 89
 				r.Get("/", hubIndexHandler)
90
-				r.Get("/touch", func(ctx *phi.Context) {
90
+				r.Get("/touch", func(ctx phi.Context) {
91 91
 					s := fmt.Sprintf("/hubs/%s/touch reqid:%s session:%s", ctx.Param("hubID"),
92 92
 						ctx.Value("requestID"), ctx.Value("session.user"))
93 93
 					ctx.Write([]byte(s))
94 94
 				})
95 95
 
96 96
 				sr3 = phi.NewRouter()
97
-				sr3.Get("/", func(ctx *phi.Context) {
97
+				sr3.Get("/", func(ctx phi.Context) {
98 98
 					s := fmt.Sprintf("/hubs/%s/webhooks reqid:%s session:%s", ctx.Param("hubID"),
99 99
 						ctx.Value("requestID"), ctx.Value("session.user"))
100 100
 					ctx.Write([]byte(s))
101 101
 				})
102 102
 				sr3.Route("/:webhookID", func(r phi.Router) {
103 103
 					sr4 = r.(*phi.Mux)
104
-					r.Get("/", func(ctx *phi.Context) {
104
+					r.Get("/", func(ctx phi.Context) {
105 105
 						s := fmt.Sprintf("/hubs/%s/webhooks/%s reqid:%s session:%s", ctx.Param("hubID"),
106 106
 							ctx.Param("webhookID"), ctx.Value("requestID"), ctx.Value("session.user"))
107 107
 						ctx.Write([]byte(s))
@@ -113,7 +113,7 @@ func TestMuxBig(t *testing.T) {
113 113
 				// perhaps add .Router() to the middleware inline thing..
114 114
 				// and use that always.. or, can detect in that method..
115 115
 				r.Mount("/webhooks", phi.Chain(func(next phi.Handler) phi.Handler {
116
-					return phi.HandlerFunc(func(ctx *phi.Context) {
116
+					return phi.HandlerFunc(func(ctx phi.Context) {
117 117
 						ctx.SetValue("hook", true)
118 118
 						next.Serve(ctx)
119 119
 					})
@@ -129,7 +129,7 @@ func TestMuxBig(t *testing.T) {
129 129
 
130 130
 				r.Route("/posts", func(r phi.Router) {
131 131
 					sr5 = r.(*phi.Mux)
132
-					r.Get("/", func(ctx *phi.Context) {
132
+					r.Get("/", func(ctx phi.Context) {
133 133
 						s := fmt.Sprintf("/hubs/%s/posts reqid:%s session:%s", ctx.Param("hubID"),
134 134
 							ctx.Value("requestID"), ctx.Value("session.user"))
135 135
 						ctx.Write([]byte(s))
@@ -140,24 +140,24 @@ func TestMuxBig(t *testing.T) {
140 140
 
141 141
 		r.Route("/folders/", func(r phi.Router) {
142 142
 			sr6 = r.(*phi.Mux)
143
-			r.Get("/", func(ctx *phi.Context) {
143
+			r.Get("/", func(ctx phi.Context) {
144 144
 				s := fmt.Sprintf("/folders/ reqid:%s session:%s",
145 145
 					ctx.Value("requestID"), ctx.Value("session.user"))
146 146
 				ctx.Write([]byte(s))
147 147
 			})
148
-			r.Get("/public", func(ctx *phi.Context) {
148
+			r.Get("/public", func(ctx phi.Context) {
149 149
 				s := fmt.Sprintf("/folders/public reqid:%s session:%s",
150 150
 					ctx.Value("requestID"), ctx.Value("session.user"))
151 151
 				ctx.Write([]byte(s))
152 152
 			})
153
-			r.Get("/in", phi.HandlerFunc(func(ctx *phi.Context) {}).Serve)
153
+			r.Get("/in", phi.HandlerFunc(func(ctx phi.Context) {}).Serve)
154 154
 
155 155
 			r.With(func(next phi.Handler) phi.Handler {
156
-				return phi.HandlerFunc(func(ctx *phi.Context) {
156
+				return phi.HandlerFunc(func(ctx phi.Context) {
157 157
 					ctx.SetValue("search", true)
158 158
 					next.Serve(ctx)
159 159
 				})
160
-			}).Get("/search", func(ctx *phi.Context) {
160
+			}).Get("/search", func(ctx phi.Context) {
161 161
 				ctx.Write([]byte("searching.."))
162 162
 			})
163 163
 		})

+ 1
- 1
docgen/markdown.go View File

@@ -7,7 +7,7 @@ import (
7 7
 	"sort"
8 8
 	"strings"
9 9
 
10
-	"maze.io/phi"
10
+	phi "maze.io/phi.v1"
11 11
 )
12 12
 
13 13
 type MarkdownDoc struct {

+ 11
- 9
middleware/closenotify.go View File

@@ -3,6 +3,8 @@ package middleware
3 3
 import (
4 4
 	"context"
5 5
 	"net/http"
6
+
7
+	phi "maze.io/phi.v1"
6 8
 )
7 9
 
8 10
 // StatusClientClosedRequest represents a 499 Client Closed Request (Nginx) HTTP status.
@@ -12,31 +14,31 @@ const StatusClientClosedRequest = 499
12 14
 // CloseNotify is a middleware that cancels ctx when the underlying
13 15
 // connection has gone away. It can be used to cancel long operations
14 16
 // on the server when the client disconnects before the response is ready.
15
-func CloseNotify(next http.Handler) http.Handler {
16
-	fn := func(w http.ResponseWriter, r *http.Request) {
17
-		cn, ok := w.(http.CloseNotifier)
17
+func CloseNotify(next phi.Handler) phi.Handler {
18
+	fn := func(ctx phi.Context) {
19
+		cn, ok := ctx.(http.CloseNotifier)
18 20
 		if !ok {
19 21
 			panic("chi/middleware: CloseNotify expects http.ResponseWriter to implement http.CloseNotifier interface")
20 22
 		}
21 23
 		closeNotifyCh := cn.CloseNotify()
22 24
 
23
-		ctx, cancel := context.WithCancel(r.Context())
25
+		rctx, cancel := context.WithCancel(ctx.Request().Context())
24 26
 		defer cancel()
25 27
 
26 28
 		go func() {
27 29
 			select {
28
-			case <-ctx.Done():
30
+			case <-rctx.Done():
29 31
 				return
30 32
 			case <-closeNotifyCh:
31 33
 				cancel()
32
-				w.WriteHeader(StatusClientClosedRequest)
34
+				ctx.WriteHeader(StatusClientClosedRequest)
33 35
 				return
34 36
 			}
35 37
 		}()
36 38
 
37
-		r = r.WithContext(ctx)
38
-		next.ServeHTTP(w, r)
39
+		ctx.SetRequest(ctx.Request().WithContext(rctx))
40
+		next.Serve(ctx)
39 41
 	}
40 42
 
41
-	return http.HandlerFunc(fn)
43
+	return phi.HandlerFunc(fn)
42 44
 }

+ 12
- 12
middleware/compress.go View File

@@ -6,6 +6,8 @@ import (
6 6
 	"io"
7 7
 	"net/http"
8 8
 	"strings"
9
+
10
+	phi "maze.io/phi.v1"
9 11
 )
10 12
 
11 13
 type encoding int
@@ -32,15 +34,13 @@ var defaultContentTypes = map[string]struct{}{
32 34
 // body of predefined content types to a data format based
33 35
 // on Accept-Encoding request header. It uses a default
34 36
 // compression level.
35
-func DefaultCompress(next http.Handler) http.Handler {
36
-	return Compress(flate.DefaultCompression)(next)
37
-}
37
+var DefaultCompress = Compress(flate.DefaultCompression)
38 38
 
39 39
 // Compress is a middleware that compresses response
40 40
 // body of a given content types to a data format based
41 41
 // on Accept-Encoding request header. It uses a given
42 42
 // compression level.
43
-func Compress(level int, types ...string) func(next http.Handler) http.Handler {
43
+func Compress(level int, types ...string) phi.Middleware {
44 44
 	contentTypes := defaultContentTypes
45 45
 	if len(types) > 0 {
46 46
 		contentTypes = make(map[string]struct{}, len(types))
@@ -49,21 +49,21 @@ func Compress(level int, types ...string) func(next http.Handler) http.Handler {
49 49
 		}
50 50
 	}
51 51
 
52
-	return func(next http.Handler) http.Handler {
53
-		fn := func(w http.ResponseWriter, r *http.Request) {
52
+	return func(next phi.Handler) phi.Handler {
53
+		fn := func(ctx phi.Context) {
54 54
 			mcw := &maybeCompressResponseWriter{
55
-				ResponseWriter: w,
56
-				w:              w,
55
+				ResponseWriter: ctx,
56
+				w:              ctx,
57 57
 				contentTypes:   contentTypes,
58
-				encoding:       selectEncoding(r.Header),
58
+				encoding:       selectEncoding(ctx.Request().Header),
59 59
 				level:          level,
60 60
 			}
61 61
 			defer mcw.Close()
62
-
63
-			next.ServeHTTP(mcw, r)
62
+			ctx.SetResponseWriter(mcw)
63
+			next.Serve(ctx)
64 64
 		}
65 65
 
66
-		return http.HandlerFunc(fn)
66
+		return phi.HandlerFunc(fn)
67 67
 	}
68 68
 }
69 69
 

+ 10
- 7
middleware/logger.go View File

@@ -11,6 +11,8 @@ import (
11 11
 	"net"
12 12
 	"net/http"
13 13
 	"time"
14
+
15
+	phi "maze.io/phi.v1"
14 16
 )
15 17
 
16 18
 // Logger is a middleware that logs the start and end of each request, along
@@ -19,11 +21,11 @@ import (
19 21
 // print in color, otherwise it will print in black and white.
20 22
 //
21 23
 // Logger prints a request ID if one is provided.
22
-func Logger(next http.Handler) http.Handler {
23
-	fn := func(w http.ResponseWriter, r *http.Request) {
24
-		reqID := GetReqID(r.Context())
25
-		prefix := requestPrefix(reqID, r)
26
-		lw := wrapWriter(w)
24
+func Logger(next phi.Handler) phi.Handler {
25
+	fn := func(ctx phi.Context) {
26
+		reqID := GetReqID(ctx)
27
+		prefix := requestPrefix(reqID, ctx.Request())
28
+		lw := wrapWriter(ctx)
27 29
 
28 30
 		t1 := time.Now()
29 31
 		defer func() {
@@ -31,10 +33,11 @@ func Logger(next http.Handler) http.Handler {
31 33
 			printRequest(prefix, reqID, lw, t2.Sub(t1))
32 34
 		}()
33 35
 
34
-		next.ServeHTTP(lw, r)
36
+		ctx.SetResponseWriter(lw)
37
+		next.Serve(ctx)
35 38
 	}
36 39
 
37
-	return http.HandlerFunc(fn)
40
+	return phi.HandlerFunc(fn)
38 41
 }
39 42
 
40 43
 func requestPrefix(reqID string, r *http.Request) *bytes.Buffer {

+ 37
- 0
middleware/middleware.go View File

@@ -0,0 +1,37 @@
1
+// Package middleware provides middlewares for phi routers.
2
+//
3
+// A middleware is a function that receives the next phi Handler as an argument,
4
+// in turn, it also functions as a phi Handler. This allows us to chain multiple
5
+// middlewares.
6
+//
7
+// A simple example that sets the Server header for each request, if the header
8
+// is not already set, may look like the following:
9
+//
10
+// Example:
11
+//  package main
12
+//
13
+//  import (
14
+//  	"net/http"
15
+//
16
+//  	phi "maze.io/phi.v1"
17
+//  )
18
+//
19
+//  func Server(next phi.Handler) phi.Handler {
20
+//    return phi.HandlerFunc(func(ctx phi.Context) {
21
+//      if ctx.Header().Get("Server") == "" {
22
+//        ctx.Header().Set("Server", "phi")
23
+//      }
24
+//    })
25
+//  }
26
+//
27
+//  func main() {
28
+//  	r := phi.NewRouter()
29
+//  	r.Use(Server)
30
+//
31
+//  	r.Get("/", func(ctx *phi.Context) {
32
+//  		ctx.Write([]byte("root."))
33
+//  	})
34
+//
35
+//  	http.ListenAndServe(":3333", r)
36
+//  }
37
+package middleware

+ 4
- 4
middleware/nocache.go View File

@@ -6,7 +6,7 @@ package middleware
6 6
 import (
7 7
 	"time"
8 8
 
9
-	"maze.io/phi"
9
+	phi "maze.io/phi.v1"
10 10
 )
11 11
 
12 12
 // Unix epoch time
@@ -38,12 +38,12 @@ var etagHeaders = []string{
38 38
 //      X-Accel-Expires: 0
39 39
 //      Pragma: no-cache (for HTTP/1.0 proxies/clients)
40 40
 func NoCache(h phi.Handler) phi.Handler {
41
-	fn := func(ctx *phi.Context) {
41
+	fn := func(ctx phi.Context) {
42 42
 
43 43
 		// Delete any ETag headers that may have been set
44 44
 		for _, v := range etagHeaders {
45
-			if ctx.Request.Header.Get(v) != "" {
46
-				ctx.Request.Header.Del(v)
45
+			if ctx.Request().Header.Get(v) != "" {
46
+				ctx.Request().Header.Del(v)
47 47
 			}
48 48
 		}
49 49
 

+ 6
- 6
middleware/profiler.go View File

@@ -6,7 +6,7 @@ import (
6 6
 	"net/http"
7 7
 	"net/http/pprof"
8 8
 
9
-	"maze.io/phi"
9
+	phi "maze.io/phi.v1"
10 10
 )
11 11
 
12 12
 // Profiler is a convenient subrouter used for mounting net/http/pprof. ie.
@@ -22,11 +22,11 @@ func Profiler() phi.Handler {
22 22
 	r := phi.NewRouter()
23 23
 	r.Use(NoCache)
24 24
 
25
-	r.Get("/", func(ctx *phi.Context) {
26
-		ctx.Redirect(ctx.Request.RequestURI+"/pprof/", http.StatusMovedPermanently)
25
+	r.Get("/", func(ctx phi.Context) {
26
+		ctx.Redirect(ctx.Request().RequestURI+"/pprof/", http.StatusMovedPermanently)
27 27
 	})
28
-	r.HandleFunc("/pprof", func(ctx *phi.Context) {
29
-		ctx.Redirect(ctx.Request.RequestURI+"/", http.StatusMovedPermanently)
28
+	r.HandleFunc("/pprof", func(ctx phi.Context) {
29
+		ctx.Redirect(ctx.Request().RequestURI+"/", http.StatusMovedPermanently)
30 30
 	})
31 31
 
32 32
 	r.HTTPHandleFunc("/pprof/", pprof.Index)
@@ -43,7 +43,7 @@ func Profiler() phi.Handler {
43 43
 }
44 44
 
45 45
 // Replicated from expvar.go as not public.
46
-func expVars(ctx *phi.Context) {
46
+func expVars(ctx phi.Context) {
47 47
 	first := true
48 48
 	ctx.Header().Set("Content-Type", "application/json; charset=utf-8")
49 49
 	fmt.Fprintf(ctx, "{\n")

+ 7
- 4
middleware/realip.go View File

@@ -6,6 +6,8 @@ package middleware
6 6
 import (
7 7
 	"net/http"
8 8
 	"strings"
9
+
10
+	phi "maze.io/phi.v1"
9 11
 )
10 12
 
11 13
 var xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For")
@@ -26,15 +28,16 @@ var xRealIP = http.CanonicalHeaderKey("X-Real-IP")
26 28
 // values from the client, or if you use this middleware without a reverse
27 29
 // proxy, malicious clients will be able to make you very sad (or, depending on
28 30
 // how you're using RemoteAddr, vulnerable to an attack of some sort).
29
-func RealIP(h http.Handler) http.Handler {
30
-	fn := func(w http.ResponseWriter, r *http.Request) {
31
+func RealIP(h phi.Handler) phi.Handler {
32
+	fn := func(ctx phi.Context) {
33
+		r := ctx.Request()
31 34
 		if rip := realIP(r); rip != "" {
32 35
 			r.RemoteAddr = rip
33 36
 		}
34
-		h.ServeHTTP(w, r)
37
+		h.Serve(ctx)
35 38
 	}
36 39
 
37
-	return http.HandlerFunc(fn)
40
+	return phi.HandlerFunc(fn)
38 41
 }
39 42
 
40 43
 func realIP(r *http.Request) string {

+ 9
- 7
middleware/recoverer.go View File

@@ -8,6 +8,8 @@ import (
8 8
 	"log"
9 9
 	"net/http"
10 10
 	"runtime/debug"
11
+
12
+	phi "maze.io/phi.v1"
11 13
 )
12 14
 
13 15
 // Recoverer is a middleware that recovers from panics, logs the panic (and a
@@ -15,22 +17,22 @@ import (
15 17
 // possible.
16 18
 //
17 19
 // Recoverer prints a request ID if one is provided.
18
-func Recoverer(next http.Handler) http.Handler {
19
-	fn := func(w http.ResponseWriter, r *http.Request) {
20
+func Recoverer(next phi.Handler) phi.Handler {
21
+	fn := func(ctx phi.Context) {
20 22
 		defer func() {
21 23
 			if err := recover(); err != nil {
22
-				reqID := GetReqID(r.Context())
23
-				prefix := requestPrefix(reqID, r)
24
+				reqID := GetReqID(ctx)
25
+				prefix := requestPrefix(reqID, ctx.Request())
24 26
 				printPanic(prefix, reqID, err)
25 27
 				debug.PrintStack()
26
-				http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
28
+				ctx.Error(nil, http.StatusInternalServerError)
27 29
 			}
28 30
 		}()
29 31
 
30
-		next.ServeHTTP(w, r)
32
+		next.Serve(ctx)
31 33
 	}
32 34
 
33
-	return http.HandlerFunc(fn)
35
+	return phi.HandlerFunc(fn)
34 36
 }
35 37
 
36 38
 func printPanic(buf *bytes.Buffer, reqID string, err interface{}) {

+ 3
- 1
middleware/request_id.go View File

@@ -12,6 +12,8 @@ import (
12 12
 	"os"
13 13
 	"strings"
14 14
 	"sync/atomic"
15
+
16
+	phi "maze.io/phi.v1"
15 17
 )
16 18
 
17 19
 // Key to use when setting the request ID.
@@ -73,7 +75,7 @@ func RequestID(next http.Handler) http.Handler {
73 75
 
74 76
 // GetReqID returns a request ID from the given context if one is present.
75 77
 // Returns the empty string if a request ID cannot be found.
76
-func GetReqID(ctx context.Context) string {
78
+func GetReqID(ctx phi.Context) string {
77 79
 	if ctx == nil {
78 80
 		return ""
79 81
 	}

+ 21
- 21
middleware/strip.go View File

@@ -3,22 +3,22 @@ package middleware
3 3
 import (
4 4
 	"net/http"
5 5
 
6
-	"maze.io/phi"
6
+	phi "maze.io/phi.v1"
7 7
 )
8 8
 
9 9
 // StripSlashes is a middleware that will match request paths with a trailing
10 10
 // slash, strip it from the path and continue routing through the mux, if a route
11 11
 // matches, then it will serve the handler.
12 12
 func StripSlashes(next phi.Handler) phi.Handler {
13
-	fn := func(ctx *phi.Context) {
14
-		var path string
15
-		if ctx.RoutePath != "" {
16
-			path = ctx.RoutePath
17
-		} else {
18
-			path = ctx.Request.URL.Path
19
-		}
20
-		if len(path) > 1 && path[len(path)-1] == '/' {
21
-			ctx.RoutePath = path[:len(path)-1]
13
+	fn := func(ctx phi.Context) {
14
+		if rctx, ok := ctx.(*phi.RoutingContext); ok {
15
+			var path string
16
+			if path = rctx.RoutePath(); path == "" {
17
+				path = rctx.Request().URL.Path
18
+			}
19
+			if len(path) > 1 && path[len(path)-1] == '/' {
20
+				rctx.SetRoutePath(path[:len(path)-1])
21
+			}
22 22
 		}
23 23
 		next.Serve(ctx)
24 24
 	}
@@ -28,17 +28,17 @@ func StripSlashes(next phi.Handler) phi.Handler {
28 28
 // RedirectSlashes is a middleware that will match request paths with a trailing
29 29
 // slash and redirect to the same path, less the trailing slash.
30 30
 func RedirectSlashes(next phi.Handler) phi.Handler {
31
-	fn := func(ctx *phi.Context) {
32
-		var path string
33
-		if ctx.RoutePath != "" {
34
-			path = ctx.RoutePath
35
-		} else {
36
-			path = ctx.Request.URL.Path
37
-		}
38
-		if len(path) > 1 && path[len(path)-1] == '/' {
39
-			path = path[:len(path)-1]
40
-			ctx.Redirect(path, http.StatusMovedPermanently)
41
-			return
31
+	fn := func(ctx phi.Context) {
32
+		if rctx, ok := ctx.(*phi.RoutingContext); ok {
33
+			var path string
34
+			if path = rctx.RoutePath(); path == "" {
35
+				path = rctx.Request().URL.Path
36
+			}
37
+			if len(path) > 1 && path[len(path)-1] == '/' {
38
+				path = path[:len(path)-1]
39
+				rctx.Redirect(path, http.StatusMovedPermanently)
40
+				return
41
+			}
42 42
 		}
43 43
 		next.Serve(ctx)
44 44
 	}

+ 10
- 10
middleware/strip_test.go View File

@@ -4,7 +4,7 @@ import (
4 4
 	"net/http/httptest"
5 5
 	"testing"
6 6
 
7
-	"maze.io/phi"
7
+	phi "maze.io/phi.v1"
8 8
 )
9 9
 
10 10
 func TestStripSlashes(t *testing.T) {
@@ -14,17 +14,17 @@ func TestStripSlashes(t *testing.T) {
14 14
 	// because then it'll be too late and will end up in a 404
15 15
 	r.Use(StripSlashes)
16 16
 
17
-	r.NotFound(func(ctx *phi.Context) {
17
+	r.NotFound(func(ctx phi.Context) {
18 18
 		ctx.WriteHeader(404)
19 19
 		ctx.Write([]byte("nothing here"))
20 20
 	})
21 21
 
22
-	r.Get("/", func(ctx *phi.Context) {
22
+	r.Get("/", func(ctx phi.Context) {
23 23
 		ctx.Write([]byte("root"))
24 24
 	})
25 25
 
26 26
 	r.Route("/accounts/:accountID", func(r phi.Router) {
27
-		r.Get("/", func(ctx *phi.Context) {
27
+		r.Get("/", func(ctx phi.Context) {
28 28
 			accountID := ctx.Param("accountID")
29 29
 			ctx.Write([]byte(accountID))
30 30
 		})
@@ -53,18 +53,18 @@ func TestStripSlashes(t *testing.T) {
53 53
 func TestStripSlashesInRoute(t *testing.T) {
54 54
 	r := phi.NewRouter()
55 55
 
56
-	r.NotFound(func(ctx *phi.Context) {
56
+	r.NotFound(func(ctx phi.Context) {
57 57
 		ctx.WriteHeader(404)
58 58
 		ctx.Write([]byte("nothing here"))
59 59
 	})
60 60
 
61
-	r.Get("/hi", func(ctx *phi.Context) {
61
+	r.Get("/hi", func(ctx phi.Context) {
62 62
 		ctx.Write([]byte("hi"))
63 63
 	})
64 64
 
65 65
 	r.Route("/accounts/:accountID", func(r phi.Router) {
66 66
 		r.Use(StripSlashes)
67
-		r.Get("/query", func(ctx *phi.Context) {
67
+		r.Get("/query", func(ctx phi.Context) {
68 68
 			accountID := ctx.Param("accountID")
69 69
 			ctx.Write([]byte(accountID))
70 70
 		})
@@ -94,17 +94,17 @@ func TestRedirectSlashes(t *testing.T) {
94 94
 	// because then it'll be too late and will end up in a 404
95 95
 	r.Use(RedirectSlashes)
96 96
 
97
-	r.NotFound(func(ctx *phi.Context) {
97
+	r.NotFound(func(ctx phi.Context) {
98 98
 		ctx.WriteHeader(404)
99 99
 		ctx.Write([]byte("nothing here"))
100 100
 	})
101 101
 
102
-	r.Get("/", func(ctx *phi.Context) {
102
+	r.Get("/", func(ctx phi.Context) {
103 103
 		ctx.Write([]byte("root"))
104 104
 	})
105 105
 
106 106
 	r.Route("/accounts/:accountID", func(r phi.Router) {
107
-		r.Get("/", func(ctx *phi.Context) {
107
+		r.Get("/", func(ctx phi.Context) {
108 108
 			accountID := ctx.Param("accountID")
109 109
 			ctx.Write([]byte(accountID))
110 110
 		})

+ 60
- 0
middleware/template/template.go View File

@@ -0,0 +1,60 @@
1
+package template
2
+
3
+import (
4
+	"net/http"
5
+
6
+	phi "maze.io/phi.v1"
7
+
8
+	"github.com/flosch/pongo2"
9
+)
10
+
11
+// Templater engine.
12
+type Templater struct {
13
+	ts *pongo2.TemplateSet
14
+}
15
+
16
+// Template initialises a new template loader from disk.
17
+func Template(path string) (*Templater, error) {
18
+	l, err := pongo2.NewLocalFileSystemLoader(path)
19
+	if err != nil {
20
+		return nil, err
21
+	}
22
+	return &Templater{pongo2.NewSet("", l)}, nil
23
+}
24
+
25
+// Context returns a context which can render templates.
26
+func (t *Templater) Context(ctx phi.Context) phi.Context {
27
+	return &Context{ctx, pongo2.Context{}, t.ts}
28
+}
29
+
30
+// ServeMiddleware injects the template function.
31
+func (t *Templater) ServeMiddleware(next phi.Handler) phi.Handler {
32
+	return phi.HandlerFunc(func(ctx phi.Context) {
33
+		next.Serve(t.Context(ctx))
34
+	})
35
+}
36
+
37
+// Context is a phi Context suitable for use with templates. It adds the
38
+// context.Template function for rendering templates.
39
+type Context struct {
40
+	phi.Context
41
+
42
+	// Data collector for the context which will be passed to the template.
43
+	Data pongo2.Context
44
+
45
+	// TemplateSet.
46
+	ts *pongo2.TemplateSet
47
+}
48
+
49
+// Template renders a template.
50
+func (ctx *Context) Template(name string) {
51
+	t, err := ctx.ts.FromFile(name)
52
+	if err != nil {
53
+		ctx.Error(err, http.StatusInternalServerError)
54
+		return
55
+	}
56
+
57
+	if err = t.ExecuteWriter(ctx.Data, ctx); err != nil {
58
+		ctx.Error(err, http.StatusInternalServerError)
59
+	}
60
+}

+ 2
- 2
middleware/throttler.go View File

@@ -5,7 +5,7 @@ import (
5 5
 	"net/http"
6 6
 	"time"
7 7
 
8
-	"maze.io/phi"
8
+	phi "maze.io/phi.v1"
9 9
 )
10 10
 
11 11
 var (
@@ -70,7 +70,7 @@ type throttler struct {
70 70
 }
71 71
 
72 72
 // Serve is the primary throttler request handler
73
-func (t *throttler) Serve(ctx *phi.Context) {
73
+func (t *throttler) Serve(ctx phi.Context) {
74 74
 	select {
75 75
 	case <-ctx.Done():
76 76
 		ctx.Error(errContextCanceled, http.StatusServiceUnavailable)

+ 5
- 5
middleware/throttler_test.go View File

@@ -9,7 +9,7 @@ import (
9 9
 	"testing"
10 10
 	"time"
11 11
 
12
-	"maze.io/phi"
12
+	phi "maze.io/phi.v1"
13 13
 )
14 14
 
15 15
 var testContent = []byte("Hello world!")
@@ -19,7 +19,7 @@ func TestThrottleBacklog(t *testing.T) {
19 19
 
20 20
 	r.Use(ThrottleBacklog(10, 50, time.Second*10))
21 21
 
22
-	r.Get("/", func(ctx *phi.Context) {
22
+	r.Get("/", func(ctx phi.Context) {
23 23
 		ctx.WriteHeader(http.StatusOK)
24 24
 		time.Sleep(time.Second * 1) // Expensive operation.
25 25
 		ctx.Write(testContent)
@@ -61,7 +61,7 @@ func TestThrottleClientTimeout(t *testing.T) {
61 61
 
62 62
 	r.Use(ThrottleBacklog(10, 50, time.Second*10))
63 63
 
64
-	r.Get("/", func(ctx *phi.Context) {
64
+	r.Get("/", func(ctx phi.Context) {
65 65
 		ctx.WriteHeader(http.StatusOK)
66 66
 		time.Sleep(time.Second * 5) // Expensive operation.
67 67
 		ctx.Write(testContent)
@@ -94,7 +94,7 @@ func TestThrottleTriggerGatewayTimeout(t *testing.T) {
94 94
 
95 95
 	r.Use(ThrottleBacklog(50, 100, time.Second*5))
96 96
 
97
-	r.Get("/", func(ctx *phi.Context) {
97
+	r.Get("/", func(ctx phi.Context) {
98 98
 		ctx.WriteHeader(http.StatusOK)
99 99
 		time.Sleep(time.Second * 10) // Expensive operation.
100 100
 		ctx.Write(testContent)
@@ -150,7 +150,7 @@ func TestThrottleMaximum(t *testing.T) {
150 150
 
151 151
 	r.Use(ThrottleBacklog(50, 50, time.Second*5))
152 152
 
153
-	r.Get("/", func(ctx *phi.Context) {
153
+	r.Get("/", func(ctx phi.Context) {
154 154
 		ctx.WriteHeader(http.StatusOK)
155 155
 		time.Sleep(time.Second * 2) // Expensive operation.
156 156
 		ctx.Write(testContent)

+ 12
- 10
middleware/timeout.go View File

@@ -4,6 +4,8 @@ import (
4 4
 	"context"
5 5
 	"net/http"
6 6
 	"time"
7
+
8
+	phi "maze.io/phi.v1"
7 9
 )
8 10
 
9 11
 // Timeout is a middleware that cancels ctx after a given timeout and return
@@ -15,7 +17,7 @@ import (
15 17
 //
16 18
 // ie. a route/handler may look like:
17 19
 //
18
-//  r.Get("/long", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
20
+//  r.Get("/long", func(ctx phi.Context) {
19 21
 // 	 processTime := time.Duration(rand.Intn(4)+1) * time.Second
20 22
 //
21 23
 // 	 select {
@@ -29,20 +31,20 @@ import (
29 31
 // 	 w.Write([]byte("done"))
30 32
 //  })
31 33
 //
32
-func Timeout(timeout time.Duration) func(next http.Handler) http.Handler {
33
-	return func(next http.Handler) http.Handler {
34
-		fn := func(w http.ResponseWriter, r *http.Request) {
35
-			ctx, cancel := context.WithTimeout(r.Context(), timeout)
34
+func Timeout(timeout time.Duration) func(next phi.Handler) phi.Handler {
35
+	return func(next phi.Handler) phi.Handler {
36
+		fn := func(ctx phi.Context) {
37
+			tctx, cancel := context.WithTimeout(ctx.Request().Context(), timeout)
36 38
 			defer func() {
37 39
 				cancel()
38
-				if ctx.Err() == context.DeadlineExceeded {
39
-					w.WriteHeader(http.StatusGatewayTimeout)
40
+				if tctx.Err() == context.DeadlineExceeded {
41
+					ctx.WriteHeader(http.StatusGatewayTimeout)
40 42
 				}
41 43
 			}()
42 44
 
43
-			r = r.WithContext(ctx)
44
-			next.ServeHTTP(w, r)
45
+			ctx.SetRequest(ctx.Request().WithContext(tctx))
46
+			next.Serve(ctx)
45 47
 		}
46
-		return http.HandlerFunc(fn)
48
+		return phi.HandlerFunc(fn)
47 49
 	}
48 50
 }

+ 0
- 17
middleware/value.go View File

@@ -1,17 +0,0 @@
1
-package middleware
2
-
3
-import (
4
-	"context"
5
-	"net/http"
6
-)
7
-
8
-// WithValue is a middleware that sets a given key/value in a context chain.
9
-func WithValue(key interface{}, val interface{}) func(next http.Handler) http.Handler {
10
-	return func(next http.Handler) http.Handler {
11
-		fn := func(w http.ResponseWriter, r *http.Request) {
12
-			r = r.WithContext(context.WithValue(r.Context(), key, val))
13
-			next.ServeHTTP(w, r)
14
-		}
15
-		return http.HandlerFunc(fn)
16
-	}
17
-}

+ 4
- 4
mux.go View File

@@ -7,7 +7,7 @@ import (
7 7
 	"sync"
8 8
 )
9 9
 
10
-var _ Router = &Mux{}
10
+var _ Router = (*Mux)(nil)
11 11
 
12 12
 // Mux is a simple HTTP route multiplexer that parses a request path,
13 13
 // records any URL params, and executes an end handler. It implements
@@ -22,7 +22,7 @@ type Mux struct {
22 22
 	tree *node
23 23
 
24 24
 	// The middleware stack
25
-	middlewares []func(Handler) Handler
25
+	middlewares []Middleware
26 26
 
27 27
 	// Controls the behaviour of middleware chain generation when a mux
28 28
 	// is registered as an inline group inside another mux.
@@ -93,7 +93,7 @@ func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
93 93
 // route to a specific handler, which provides opportunity to respond early,
94 94
 // change the course of the request execution, or set request-scoped values for
95 95
 // the next http.Handler.
96
-func (mx *Mux) Use(middlewares ...func(Handler) Handler) {
96
+func (mx *Mux) Use(middlewares ...Middleware) {
97 97
 	if mx.handler != nil {
98 98
 		panic("phi: all middlewares must be defined before routes on a mux")
99 99
 	}
@@ -185,7 +185,7 @@ func (mx *Mux) NotFound(handlerFn HandlerFunc) {
185 185
 }
186 186
 
187 187
 // With adds inline middlewares for an endpoint handler.
188
-func (mx *Mux) With(middlewares ...func(Handler) Handler) Router {
188
+func (mx *Mux) With(middlewares ...Middleware) Router {
189 189
 	// Similarly as in handle(), we must build the mux handler once further
190 190
 	// middleware registration isn't allowed for this stack, like now.
191 191
 	if !mx.inline && mx.handler == nil {

+ 10
- 7
phi.go View File

@@ -44,6 +44,13 @@ func (f HandlerFunc) Serve(ctx Context) {
44 44
 	f(ctx)
45 45
 }
46 46
 
47
+// Middleware can handle phi middleware.
48
+type Middleware func(Handler) Handler
49
+
50
+// Middlewares type is a slice of standard middleware handlers with methods
51
+// to compose middleware chains and Handler's.
52
+type Middlewares []Middleware
53
+
47 54
 // NewRouter returns a new Mux object that implements the Router interface.
48 55
 func NewRouter() *Mux {
49 56
 	return NewMux()
@@ -55,11 +62,11 @@ type Router interface {
55 62
 	http.Handler
56 63
 	Routes
57 64
 
58
-	// Use appends one of more middlewares onto the Router stack.
59
-	Use(middlewares ...func(Handler) Handler)
65
+	// Use appends one or more middlewares onto the Router stack.
66
+	Use(middlewares ...Middleware)
60 67
 
61 68
 	// With adds inline middlewares for an endpoint handler.
62
-	With(middlewares ...func(Handler) Handler) Router
69
+	With(middlewares ...Middleware) Router
63 70
 
64 71
 	// Group adds a new inline-Router along the current routing
65 72
 	// path, with a fresh middleware stack for the inline-Router.
@@ -101,7 +108,3 @@ type Routes interface {
101 108
 	// Middlewares returns the list of middlewares in use by the router.
102 109
 	Middlewares() Middlewares
103 110
 }
104
-
105
-// Middlewares type is a slice of standard middleware handlers with methods
106
-// to compose middleware chains and Handler's.
107
-type Middlewares []func(Handler) Handler

+ 0
- 48
template/template.go View File

@@ -1,48 +0,0 @@
1
-package content
2
-
3
-import (
4
-	"errors"
5
-
6
-	"maze.io/phi"
7
-
8
-	"github.com/flosch/pongo2"
9
-)
10
-
11
-type Template struct {
12
-	ts *pongo2.TemplateSet
13
-}
14
-
15
-// New initialises a new template loader from disk.
16
-func New(path string) (*Template, error) {
17
-	l, err := pongo2.NewLocalFileSystemLoader(path)
18
-	if err != nil {
19
-		return nil, err
20
-	}
21
-	return &Template{pongo2.NewSet("", l)}, nil
22
-}
23
-
24
-// Render the named template to the context.
25
-func (t *Template) Render(ctx *phi.Context, v interface{}) (err error) {
26
-	var (
27
-		name string
28
-		ok   bool
29
-	)
30
-	if name, ok = v.(string); !ok {
31
-		return errors.New(`phi: template name must be string`)
32
-	}
33
-
34
-	var template *pongo2.Template
35
-	if template, err = t.ts.FromFile(name); err != nil {
36
-		return
37
-	}
38
-
39
-	return template.ExecuteWriter(pongo2.Context(ctx.Data), ctx)
40
-}
41
-
42
-// Register this template loader for the specified content types.
43
-func (t *Template) Register(contentTypes ...string) {
44
-	for _, contentType := range contentTypes {
45
-		phi.UnregisterContentType(contentType)
46
-	}
47
-	phi.Register(t, contentTypes...)
48
-}

Loading…
Cancel
Save