Skip to content

Commit 0e41cfd

Browse files
Dean KarnDean Karn
authored andcommitted
updated to use Go 1.7's *http.Request Context
- Remains backward compatible with 1.6 - See special notes section in README
1 parent 32f438e commit 0e41cfd

File tree

8 files changed

+586
-240
lines changed

8 files changed

+586
-240
lines changed

README.md

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
##LARS
22
<img align="right" src="https://raw.githubusercontent.com/go-playground/lars/master/examples/README/test.gif">
3-
![Project status](https://img.shields.io/badge/version-3.2.0-green.svg)
3+
![Project status](https://img.shields.io/badge/version-3.3.0-green.svg)
44
[![Build Status](https://semaphoreci.com/api/v1/projects/4351aa2d-2f94-40be-a6ef-85c248490378/679708/badge.svg)](https://semaphoreci.com/joeybloggs/lars)
55
[![Coverage Status](https://coveralls.io/repos/github/go-playground/lars/badge.svg?branch=master)](https://coveralls.io/github/go-playground/lars?branch=master)
66
[![Go Report Card](https://goreportcard.com/badge/go-playground/lars)](https://goreportcard.com/report/go-playground/lars)
@@ -233,6 +233,26 @@ l.Use(nosurf.NewPure(lars.NativeChainHandler))
233233
// The functions are for convenience and are totally optional.
234234
```
235235

236+
Special Note
237+
-------------
238+
I don't know if it was an oversight or just an assumption about how middleware would be used with Go 1.7's new
239+
`context` integration into the `*http.Request` but there are a few quirks. As you know lars handles multiple handler
240+
types, including the native handler, this functionality is possible because of the way lar handles the middleware; lars
241+
does not `chain` the middleware in the normal way, but rather calles each in sequence; because of this all you have to
242+
do is call c.Next() or it has already been wrapped to do so for you transparently. OK getting back to the point, if you
243+
are not using `lars.Context` to set the context information you will have to set the request object so that the information
244+
gets back to the calling package. eg.
245+
246+
```go
247+
// because 'r' is a copy of a pointer to allow the information to get
248+
// back to the caller, need to set the value of 'r' as below with '*r'
249+
func(w http.ResponseWriter, r *http.Request) {
250+
*r = *r.WithContext(context.WithValue(r.Context(), 0, "testval1"))
251+
}
252+
```
253+
254+
this is not an issue specific to lars, but a quirk of the way `context` is tied to the `http.Request` object.
255+
236256
Middleware
237257
-----------
238258
There are some pre-defined middlewares within the middleware folder; NOTE: that the middleware inside will
@@ -245,41 +265,40 @@ recovery middleware are very application dependent and therefore will be listed
245265

246266
Benchmarks
247267
-----------
248-
Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go version go1.6 darwin/amd64
268+
Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go version go1.7 darwin/amd64
249269

250270
NOTICE: lars uses a custom version of [httprouter](https://github.com/julienschmidt/httprouter), benchmarks can be found [here](https://github.com/joeybloggs/go-http-routing-benchmark/tree/lars-only)
251271

252272
```go
253273
go test -bench=. -benchmem=true
254274
#GithubAPI Routes: 203
255-
LARS: 49040 Bytes
275+
LARS: 49032 Bytes
256276

257277
#GPlusAPI Routes: 13
258-
LARS: 3648 Bytes
278+
LARS: 3640 Bytes
259279

260280
#ParseAPI Routes: 26
261-
LARS: 6640 Bytes
281+
LARS: 6632 Bytes
262282

263283
#Static Routes: 157
264-
LARS: 30128 Bytes
265-
266-
PASS
267-
BenchmarkLARS_Param 20000000 77.1 ns/op 0 B/op 0 allocs/op
268-
BenchmarkLARS_Param5 10000000 134 ns/op 0 B/op 0 allocs/op
269-
BenchmarkLARS_Param20 5000000 320 ns/op 0 B/op 0 allocs/op
270-
BenchmarkLARS_ParamWrite 10000000 142 ns/op 0 B/op 0 allocs/op
271-
BenchmarkLARS_GithubStatic 20000000 96.2 ns/op 0 B/op 0 allocs/op
272-
BenchmarkLARS_GithubParam 10000000 156 ns/op 0 B/op 0 allocs/op
273-
BenchmarkLARS_GithubAll 50000 32952 ns/op 0 B/op 0 allocs/op
274-
BenchmarkLARS_GPlusStatic 20000000 72.2 ns/op 0 B/op 0 allocs/op
275-
BenchmarkLARS_GPlusParam 20000000 98.0 ns/op 0 B/op 0 allocs/op
276-
BenchmarkLARS_GPlus2Params 10000000 127 ns/op 0 B/op 0 allocs/op
277-
BenchmarkLARS_GPlusAll 1000000 1619 ns/op 0 B/op 0 allocs/op
278-
BenchmarkLARS_ParseStatic 20000000 72.8 ns/op 0 B/op 0 allocs/op
279-
BenchmarkLARS_ParseParam 20000000 78.6 ns/op 0 B/op 0 allocs/op
280-
BenchmarkLARS_Parse2Params 20000000 96.9 ns/op 0 B/op 0 allocs/op
281-
BenchmarkLARS_ParseAll 500000 2968 ns/op 0 B/op 0 allocs/op
282-
BenchmarkLARS_StaticAll 100000 22810 ns/op 0 B/op 0 allocs/op
284+
LARS: 30120 Bytes
285+
286+
BenchmarkLARS_Param 20000000 70.2 ns/op 0 B/op 0 allocs/op
287+
BenchmarkLARS_Param5 20000000 104 ns/op 0 B/op 0 allocs/op
288+
BenchmarkLARS_Param20 5000000 248 ns/op 0 B/op 0 allocs/op
289+
BenchmarkLARS_ParamWrite 10000000 134 ns/op 0 B/op 0 allocs/op
290+
BenchmarkLARS_GithubStatic 20000000 84.2 ns/op 0 B/op 0 allocs/op
291+
BenchmarkLARS_GithubParam 10000000 129 ns/op 0 B/op 0 allocs/op
292+
BenchmarkLARS_GithubAll 50000 25334 ns/op 0 B/op 0 allocs/op
293+
BenchmarkLARS_GPlusStatic 20000000 67.0 ns/op 0 B/op 0 allocs/op
294+
BenchmarkLARS_GPlusParam 20000000 84.5 ns/op 0 B/op 0 allocs/op
295+
BenchmarkLARS_GPlus2Params 20000000 103 ns/op 0 B/op 0 allocs/op
296+
BenchmarkLARS_GPlusAll 1000000 1135 ns/op 0 B/op 0 allocs/op
297+
BenchmarkLARS_ParseStatic 20000000 67.5 ns/op 0 B/op 0 allocs/op
298+
BenchmarkLARS_ParseParam 20000000 74.0 ns/op 0 B/op 0 allocs/op
299+
BenchmarkLARS_Parse2Params 20000000 86.9 ns/op 0 B/op 0 allocs/op
300+
BenchmarkLARS_ParseAll 1000000 2029 ns/op 0 B/op 0 allocs/op
301+
BenchmarkLARS_StaticAll 100000 18157 ns/op 0 B/op 0 allocs/op
283302
```
284303

285304
Package Versioning

context.go

Lines changed: 1 addition & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package lars
22

33
import (
4+
"context"
45
"encoding/json"
56
"encoding/xml"
67
"io"
@@ -10,7 +11,6 @@ import (
1011
"time"
1112

1213
"github.com/gorilla/websocket"
13-
"golang.org/x/net/context"
1414
)
1515

1616
// Param is a single URL parameter, consisting of a key and a value.
@@ -61,21 +61,6 @@ type Context interface {
6161
BaseContext() *Ctx
6262
}
6363

64-
// Ctx encapsulates the http request, response context
65-
type Ctx struct {
66-
netContext context.Context
67-
request *http.Request
68-
response *Response
69-
websocket *websocket.Conn
70-
params Params
71-
handlers HandlersChain
72-
parent Context
73-
handlerName string
74-
index int
75-
formParsed bool
76-
multipartFormParsed bool
77-
}
78-
7964
var _ context.Context = &Ctx{}
8065

8166
// NewContext returns a new default lars Context object.
@@ -117,18 +102,6 @@ func (c *Ctx) WebSocket() *websocket.Conn {
117102
func (c *Ctx) RequestEnd() {
118103
}
119104

120-
// RequestStart resets the Context to it's default request state
121-
func (c *Ctx) RequestStart(w http.ResponseWriter, r *http.Request) {
122-
c.request = r
123-
c.response.reset(w)
124-
c.params = c.params[0:0]
125-
c.netContext = context.Background() // in go 1.7 will call r.Context(), netContext will go away and be replaced with the Request objects Context
126-
c.index = -1
127-
c.handlers = nil
128-
c.formParsed = false
129-
c.multipartFormParsed = false
130-
}
131-
132105
// Param returns the value of the first Param which key matches the given name.
133106
// If no matching Param is found, an empty string is returned.
134107
func (c *Ctx) Param(name string) string {
@@ -189,22 +162,6 @@ func (c *Ctx) ParseMultipartForm(maxMemory int64) error {
189162
return nil
190163
}
191164

192-
// Set is used to store a new key/value pair using the
193-
// golang.org/x/net/context contained on this Context.
194-
// It is a shortcut for context.WithValue(..., ...)
195-
func (c *Ctx) Set(key interface{}, value interface{}) {
196-
c.netContext = context.WithValue(c.netContext, key, value)
197-
}
198-
199-
// Get returns the value for the given key and is a shortcut
200-
// for the golang.org/x/net/context context.Value(...) ... but it
201-
// also returns if the value was found or not.
202-
func (c *Ctx) Get(key interface{}) (value interface{}, exists bool) {
203-
value = c.netContext.Value(key)
204-
exists = value != nil
205-
return
206-
}
207-
208165
// Next should be used only inside middleware.
209166
// It executes the pending handlers in the chain inside the calling handler.
210167
// See example in github.
@@ -457,67 +414,3 @@ func (c *Ctx) Decode(includeFormQueryParams bool, maxMemory int64, v interface{}
457414
}
458415
return
459416
}
460-
461-
// golang.org/x/net/context functions to comply with context.Context interface and keep context update on lars.Context object
462-
463-
// Context returns the request's context. To change the context, use
464-
// WithContext.
465-
//
466-
// The returned context is always non-nil.
467-
func (c *Ctx) Context() context.Context {
468-
return c.netContext // TODO: in go 1.7 return c.request.Context()
469-
}
470-
471-
// WithContext updates the underlying request's context with to ctx
472-
// The provided ctx must be non-nil.
473-
func (c *Ctx) WithContext(ctx context.Context) {
474-
c.netContext = ctx // TODO: in go 1.7 must update Request object after calling c.request.WithContext(...)
475-
}
476-
477-
// Deadline calls the underlying golang.org/x/net/context Deadline()
478-
func (c *Ctx) Deadline() (deadline time.Time, ok bool) {
479-
return c.netContext.Deadline()
480-
}
481-
482-
// Done calls the underlying golang.org/x/net/context Done()
483-
func (c *Ctx) Done() <-chan struct{} {
484-
return c.netContext.Done()
485-
}
486-
487-
// Err calls the underlying golang.org/x/net/context Err()
488-
func (c *Ctx) Err() error {
489-
return c.netContext.Err()
490-
}
491-
492-
// Value calls the underlying golang.org/x/net/context Value()
493-
func (c *Ctx) Value(key interface{}) interface{} {
494-
return c.netContext.Value(key)
495-
}
496-
497-
// WithCancel calls golang.org/x/net/context WithCancel and automatically
498-
// updates context on the containing las.Context object.
499-
func (c *Ctx) WithCancel() (cf context.CancelFunc) {
500-
c.netContext, cf = context.WithCancel(c.netContext)
501-
return
502-
}
503-
504-
// WithDeadline calls golang.org/x/net/context WithDeadline and automatically
505-
// updates context on the containing las.Context object.
506-
func (c *Ctx) WithDeadline(deadline time.Time) (cf context.CancelFunc) {
507-
c.netContext, cf = context.WithDeadline(c.netContext, deadline)
508-
return
509-
}
510-
511-
// WithTimeout calls golang.org/x/net/context WithTimeout and automatically
512-
// updates context on the containing las.Context object.
513-
func (c *Ctx) WithTimeout(timeout time.Duration) (cf context.CancelFunc) {
514-
c.netContext, cf = context.WithTimeout(c.netContext, timeout)
515-
return
516-
}
517-
518-
// WithValue calls golang.org/x/net/context WithValue and automatically
519-
// updates context on the containing las.Context object.
520-
// Can also use Set() function on Context object (Recommended)
521-
func (c *Ctx) WithValue(key interface{}, val interface{}) {
522-
c.netContext = context.WithValue(c.netContext, key, val)
523-
}

context_16.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// +build !go1.7
2+
3+
package lars
4+
5+
import (
6+
"context"
7+
"net/http"
8+
"time"
9+
10+
"github.com/gorilla/websocket"
11+
)
12+
13+
// Ctx encapsulates the http request, response context
14+
type Ctx struct {
15+
netContext context.Context
16+
request *http.Request
17+
response *Response
18+
websocket *websocket.Conn
19+
params Params
20+
handlers HandlersChain
21+
parent Context
22+
handlerName string
23+
index int
24+
formParsed bool
25+
multipartFormParsed bool
26+
}
27+
28+
// RequestStart resets the Context to it's default request state
29+
func (c *Ctx) RequestStart(w http.ResponseWriter, r *http.Request) {
30+
c.request = r
31+
c.response.reset(w)
32+
c.params = c.params[0:0]
33+
c.netContext = context.Background() // in go 1.7 will call r.Context(), netContext will go away and be replaced with the Request objects Context
34+
c.index = -1
35+
c.handlers = nil
36+
c.formParsed = false
37+
c.multipartFormParsed = false
38+
}
39+
40+
// Set is used to store a new key/value pair using the
41+
// golang.org/x/net/context contained on this Context.
42+
// It is a shortcut for context.WithValue(..., ...)
43+
func (c *Ctx) Set(key interface{}, value interface{}) {
44+
c.netContext = context.WithValue(c.netContext, key, value)
45+
}
46+
47+
// Get returns the value for the given key and is a shortcut
48+
// for the golang.org/x/net/context context.Value(...) ... but it
49+
// also returns if the value was found or not.
50+
func (c *Ctx) Get(key interface{}) (value interface{}, exists bool) {
51+
value = c.netContext.Value(key)
52+
exists = value != nil
53+
return
54+
}
55+
56+
// golang.org/x/net/context functions to comply with context.Context interface and keep context update on lars.Context object
57+
58+
// Context returns the request's context. To change the context, use
59+
// WithContext.
60+
//
61+
// The returned context is always non-nil.
62+
func (c *Ctx) Context() context.Context {
63+
return c.netContext // TODO: in go 1.7 return c.request.Context()
64+
}
65+
66+
// WithContext updates the underlying request's context with to ctx
67+
// The provided ctx must be non-nil.
68+
func (c *Ctx) WithContext(ctx context.Context) {
69+
c.netContext = ctx // TODO: in go 1.7 must update Request object after calling c.request.WithContext(...)
70+
}
71+
72+
// Deadline calls the underlying golang.org/x/net/context Deadline()
73+
func (c *Ctx) Deadline() (deadline time.Time, ok bool) {
74+
return c.netContext.Deadline()
75+
}
76+
77+
// Done calls the underlying golang.org/x/net/context Done()
78+
func (c *Ctx) Done() <-chan struct{} {
79+
return c.netContext.Done()
80+
}
81+
82+
// Err calls the underlying golang.org/x/net/context Err()
83+
func (c *Ctx) Err() error {
84+
return c.netContext.Err()
85+
}
86+
87+
// Value calls the underlying golang.org/x/net/context Value()
88+
func (c *Ctx) Value(key interface{}) interface{} {
89+
return c.netContext.Value(key)
90+
}
91+
92+
// WithCancel calls golang.org/x/net/context WithCancel and automatically
93+
// updates context on the containing las.Context object.
94+
func (c *Ctx) WithCancel() (cf context.CancelFunc) {
95+
c.netContext, cf = context.WithCancel(c.netContext)
96+
return
97+
}
98+
99+
// WithDeadline calls golang.org/x/net/context WithDeadline and automatically
100+
// updates context on the containing las.Context object.
101+
func (c *Ctx) WithDeadline(deadline time.Time) (cf context.CancelFunc) {
102+
c.netContext, cf = context.WithDeadline(c.netContext, deadline)
103+
return
104+
}
105+
106+
// WithTimeout calls golang.org/x/net/context WithTimeout and automatically
107+
// updates context on the containing las.Context object.
108+
func (c *Ctx) WithTimeout(timeout time.Duration) (cf context.CancelFunc) {
109+
c.netContext, cf = context.WithTimeout(c.netContext, timeout)
110+
return
111+
}
112+
113+
// WithValue calls golang.org/x/net/context WithValue and automatically
114+
// updates context on the containing las.Context object.
115+
// Can also use Set() function on Context object (Recommended)
116+
func (c *Ctx) WithValue(key interface{}, val interface{}) {
117+
c.netContext = context.WithValue(c.netContext, key, val)
118+
}

0 commit comments

Comments
 (0)