1
- // +build go1.7
2
-
3
1
package restlayer
4
2
5
3
import (
6
4
"context"
5
+ "fmt"
6
+ "log"
7
7
"net/http"
8
8
"net/url"
9
+ "os"
9
10
"time"
10
11
11
- "github.com/justinas/alice"
12
12
"github.com/rs/cors"
13
- "github.com/rs/rest-layer/resource/testing/mem"
13
+
14
14
"github.com/rs/rest-layer/resource"
15
+ "github.com/rs/rest-layer/resource/testing/mem"
15
16
"github.com/rs/rest-layer/rest"
16
17
"github.com/rs/rest-layer/schema"
17
- "github.com/rs/xaccess"
18
- "github.com/rs/zerolog"
19
- "github.com/rs/zerolog/hlog"
20
- "github.com/rs/zerolog/log"
21
18
)
22
19
20
+ // ResponseRecorder extends http.ResponseWriter with the ability to capture
21
+ // the status and number of bytes written
22
+ type ResponseRecorder struct {
23
+ http.ResponseWriter
24
+
25
+ statusCode int
26
+ length int
27
+ }
28
+
29
+ // NewResponseRecorder returns a ResponseRecorder that wraps w.
30
+ func NewResponseRecorder (w http.ResponseWriter ) * ResponseRecorder {
31
+ return & ResponseRecorder {ResponseWriter : w , statusCode : http .StatusOK }
32
+ }
33
+
34
+ // Write writes b to the underlying response writer and stores how many bytes
35
+ // have been written.
36
+ func (w * ResponseRecorder ) Write (b []byte ) (n int , err error ) {
37
+ n , err = w .ResponseWriter .Write (b )
38
+ w .length += n
39
+ return
40
+ }
41
+
42
+ // WriteHeader stores and writes the HTTP status code.
43
+ func (w * ResponseRecorder ) WriteHeader (code int ) {
44
+ w .statusCode = code
45
+ w .ResponseWriter .WriteHeader (code )
46
+ }
47
+
48
+ // StatusCode returns the status-code written to the response or 200 (OK).
49
+ func (w * ResponseRecorder ) StatusCode () int {
50
+ if w .statusCode == 0 {
51
+ return http .StatusOK
52
+ }
53
+ return w .statusCode
54
+ }
55
+
56
+ func AccessLog (next http.Handler ) http.Handler {
57
+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
58
+ start := time .Now ()
59
+ rec := NewResponseRecorder (w )
60
+
61
+ next .ServeHTTP (rec , r )
62
+ status := rec .StatusCode ()
63
+ length := rec .length
64
+
65
+ // In this example we use the standard library logger. Structured logs
66
+ // may prove more parsable in a production environment.
67
+ log .Printf ("D! Served HTTP Request %s %s with Response %d %s [%d bytes] in %d ms" ,
68
+ r .Method ,
69
+ r .URL ,
70
+ status ,
71
+ http .StatusText (status ),
72
+ length ,
73
+ time .Since (start ).Nanoseconds ()/ 1e6 ,
74
+ )
75
+ })
76
+ }
77
+
78
+ var logLevelPrefixes = map [resource.LogLevel ]string {
79
+ resource .LogLevelFatal : "E!" ,
80
+ resource .LogLevelError : "E!" ,
81
+ resource .LogLevelWarn : "W!" ,
82
+ resource .LogLevelInfo : "I!" ,
83
+ resource .LogLevelDebug : "D!" ,
84
+ }
85
+
23
86
func Example () {
87
+ // Configure a log-addapter for the resource pacakge.
88
+ resource .LoggerLevel = resource .LogLevelDebug
89
+ resource .Logger = func (ctx context.Context , level resource.LogLevel , msg string , fields map [string ]interface {}) {
90
+ fmt .Printf ("%s %s %v" , logLevelPrefixes [level ], msg , fields )
91
+ }
92
+
24
93
var (
25
94
// Define a user resource schema
26
95
user = schema.Schema {
@@ -115,12 +184,12 @@ func Example() {
115
184
}
116
185
)
117
186
118
- // Create a REST API root resource
187
+ // Create a REST API root resource.
119
188
index := resource .NewIndex ()
120
189
121
190
// Add a resource on /users[/:user_id]
122
191
users := index .Bind ("users" , user , mem .NewHandler (), resource.Conf {
123
- // We allow all REST methods
192
+ // We allow all REST methods.
124
193
// (rest.ReadWrite is a shortcut for []rest.Mode{Create, Read, Update, Delete, List})
125
194
AllowedModes : resource .ReadWrite ,
126
195
})
@@ -132,53 +201,32 @@ func Example() {
132
201
AllowedModes : []resource.Mode {resource .Read , resource .List , resource .Create , resource .Delete },
133
202
})
134
203
135
- // Add a friendly alias to public posts
204
+ // Add a friendly alias to public posts.
136
205
// (equivalent to /users/:user_id/posts?filter={"public":true})
137
206
posts .Alias ("public" , url.Values {"filter" : []string {"{\" public\" =true}" }})
138
207
139
- // Create API HTTP handler for the resource graph
208
+ // Create API HTTP handler for the resource graph.
209
+ var api http.Handler
140
210
api , err := rest .NewHandler (index )
141
211
if err != nil {
142
- log .Fatal ().Err (err ).Msg ("Invalid API configuration" )
212
+ log .Printf ("E! Invalid API configuration: %s" , err )
213
+ os .Exit (1 )
143
214
}
144
215
145
- // Init an alice handler chain (use your preferred one)
146
- c := alice .New ()
147
-
148
- // Install a logger
149
- c = c .Append (hlog .NewHandler (log .With ().Logger ()))
150
- c = c .Append (hlog .AccessHandler (func (r * http.Request , status , size int , duration time.Duration ) {
151
- hlog .FromRequest (r ).Info ().
152
- Str ("method" , r .Method ).
153
- Str ("url" , r .URL .String ()).
154
- Int ("status" , status ).
155
- Int ("size" , size ).
156
- Dur ("duration" , duration ).
157
- Msg ("" )
158
- }))
159
- c = c .Append (hlog .RequestHandler ("req" ))
160
- c = c .Append (hlog .RemoteAddrHandler ("ip" ))
161
- c = c .Append (hlog .UserAgentHandler ("ua" ))
162
- c = c .Append (hlog .RefererHandler ("ref" ))
163
- c = c .Append (hlog .RequestIDHandler ("req_id" , "Request-Id" ))
164
- resource .LoggerLevel = resource .LogLevelDebug
165
- resource .Logger = func (ctx context.Context , level resource.LogLevel , msg string , fields map [string ]interface {}) {
166
- zerolog .Ctx (ctx ).WithLevel (zerolog .Level (level )).Fields (fields ).Msg (msg )
167
- }
168
-
169
- // Log API access
170
- c = c .Append (xaccess .NewHandler ())
171
-
172
216
// Add CORS support with passthrough option on so rest-layer can still
173
- // handle OPTIONS method
174
- c = c .Append (cors .New (cors.Options {OptionsPassthrough : true }).Handler )
217
+ // handle OPTIONS method.
218
+ api = cors .New (cors.Options {OptionsPassthrough : true }).Handler (api )
219
+
220
+ // Wrap the api & cors handler with an access log middleware.
221
+ api = AccessLog (api )
175
222
176
- // Bind the API under /api/ path
177
- http .Handle ("/api/" , http .StripPrefix ("/api/" , c . Then ( api ) ))
223
+ // Bind the API under the /api/ path.
224
+ http .Handle ("/api/" , http .StripPrefix ("/api/" , api ))
178
225
179
- // Serve it
180
- log .Info (). Msg ( " Serving API on http://localhost:8080" )
226
+ // Serve it.
227
+ log .Printf ( "I! Serving API on http://localhost:8080" )
181
228
if err := http .ListenAndServe (":8080" , nil ); err != nil {
182
- log .Fatal ().Err (err ).Msg ("" )
229
+ log .Printf ("E! Cannot serve API: %s" , err )
230
+ os .Exit (1 )
183
231
}
184
232
}
0 commit comments