Skip to content

Commit 98abf6d

Browse files
committed
Start work on goa.Context deprecation
1 parent 817490c commit 98abf6d

9 files changed

+171
-270
lines changed

context.go

+86-174
Original file line numberDiff line numberDiff line change
@@ -6,221 +6,133 @@ import (
66
"net/url"
77

88
"golang.org/x/net/context"
9-
log "gopkg.in/inconshreveable/log15.v2"
109
)
1110

12-
// Context is the object that provides access to the underlying HTTP request and response state.
13-
// Context implements http.ResponseWriter and also provides helper methods for writing HTTP responses.
14-
// It also implements the context.Context interface described at http://blog.golang.org/context.
15-
type Context struct {
16-
context.Context // Underlying context
17-
log.Logger // Context logger
18-
}
19-
20-
// key is the type used to store internal values in the context.
21-
// Context provides typed accessor methods to these values.
22-
type key int
23-
11+
// Keys used to store data in context.
2412
const (
25-
serviceKey = iota
26-
reqKey
13+
reqKey key = iota + 1
2714
respKey
28-
paramsKey
29-
payloadKey
30-
respWrittenKey
31-
respStatusKey
32-
respLenKey
3315
)
3416

35-
// NewContext builds a goa context from the given context.Context and request state.
36-
// If gctx is nil then context.Background is used instead.
37-
func NewContext(gctx context.Context,
38-
service Service,
39-
req *http.Request,
40-
rw http.ResponseWriter,
41-
params url.Values) *Context {
42-
43-
if gctx == nil {
44-
gctx = context.Background()
17+
type (
18+
// RequestData provides access to the underlying HTTP request.
19+
RequestData struct {
20+
*http.Request
21+
22+
// Action is the name of the resource action targeted by the request
23+
Action string
24+
// Controller is the name of the resource controller targeted by the request
25+
Controller string
26+
// Service is the service targeted by the request
27+
Service Service
28+
// Payload returns the decoded request body.
29+
Payload interface{}
30+
// Params is the path and querystring request parameters.
31+
Params url.Values
4532
}
46-
gctx = context.WithValue(gctx, serviceKey, service)
47-
gctx = context.WithValue(gctx, reqKey, req)
48-
gctx = context.WithValue(gctx, respKey, rw)
49-
gctx = context.WithValue(gctx, paramsKey, params)
50-
51-
return &Context{Context: gctx}
52-
}
53-
54-
// SetPayload initializes the unmarshaled request body value.
55-
func (ctx *Context) SetPayload(payload interface{}) {
56-
ctx.SetValue(payloadKey, payload)
57-
}
58-
59-
// SetValue sets the value associated with key in the context.
60-
// The value can be retrieved using the Value method.
61-
// Note that this changes the underlying context.Context object and thus clients holding a reference
62-
// to that won't be able to access the new value. It's probably a bad idea to hold a reference to
63-
// the inner context anyway...
64-
func (ctx *Context) SetValue(key, val interface{}) {
65-
ctx.Context = context.WithValue(ctx.Context, key, val)
66-
}
6733

68-
// SetResponseWriter overrides the context underlying response writer. It returns the response
69-
// writer that was previously set.
70-
func (ctx *Context) SetResponseWriter(rw http.ResponseWriter) http.ResponseWriter {
71-
rwo := ctx.Value(respKey)
72-
ctx.SetValue(respKey, rw)
73-
if rwo == nil {
74-
return nil
75-
}
76-
return rwo.(http.ResponseWriter)
77-
}
34+
// ResponseData provides access to the underlying HTTP response.
35+
ResponseData struct {
36+
http.ResponseWriter
37+
ctx context.Context // for access to the encoder
7838

79-
// Service returns the underlying service.
80-
func (ctx *Context) Service() Service {
81-
s := ctx.Value(serviceKey)
82-
if s != nil {
83-
return s.(Service)
39+
// Status is the response HTTP status code
40+
Status int
41+
// Len is the response body length
42+
Len int
8443
}
85-
return nil
86-
}
8744

88-
// Request returns the underlying HTTP request.
89-
func (ctx *Context) Request() *http.Request {
90-
r := ctx.Value(reqKey)
91-
if r != nil {
92-
return r.(*http.Request)
93-
}
94-
return nil
95-
}
45+
// key is the type used to store internal values in the context.
46+
// Context provides typed accessor methods to these values.
47+
key int
48+
)
9649

97-
// ResponseWritten returns true if an HTTP response was written.
98-
func (ctx *Context) ResponseWritten() bool {
99-
if wr := ctx.Value(respStatusKey); wr != nil {
100-
return true
101-
}
102-
return false
103-
}
50+
// NewContext builds a goa context from the given context.Context and request state.
51+
// If gctx is nil then RootContext is used.
52+
func NewContext(gctx context.Context, ctrl *ApplicationController, action string,
53+
req *http.Request, rw http.ResponseWriter, params url.Values) context.Context {
10454

105-
// ResponseStatus returns the response status if it was set via one of the context response
106-
// methods (Respond, JSON, BadRequest, Bug), 0 otherwise.
107-
func (ctx *Context) ResponseStatus() int {
108-
if is := ctx.Value(respStatusKey); is != nil {
109-
return is.(int)
55+
if gctx == nil {
56+
gctx = RootContext
11057
}
111-
return 0
112-
}
113-
114-
// ResponseLength returns the response body length in bytes if the response was written to the
115-
// context via one of the response methods (Respond, JSON, BadRequest, Bug), 0 otherwise.
116-
func (ctx *Context) ResponseLength() int {
117-
if is := ctx.Value(respLenKey); is != nil {
118-
return is.(int)
58+
request := &RequestData{
59+
Request: req,
60+
Params: params,
61+
Action: action,
62+
Controller: ctrl.Name,
63+
Service: ctrl.app,
11964
}
120-
return 0
121-
}
65+
response := &ResponseData{ResponseWriter: rw}
66+
gctx = context.WithValue(gctx, reqKey, request)
67+
gctx = context.WithValue(gctx, respKey, response)
68+
response.ctx = gctx
12269

123-
// Get returns the param or querystring value with the given name.
124-
func (ctx *Context) Get(name string) string {
125-
iparams := ctx.Value(paramsKey)
126-
if iparams == nil {
127-
return ""
128-
}
129-
params := iparams.(url.Values)
130-
return params.Get(name)
70+
return gctx
13171
}
13272

133-
// GetMany returns the querystring values with the given name or nil if there aren't any.
134-
func (ctx *Context) GetMany(name string) []string {
135-
iparams := ctx.Value(paramsKey)
136-
if iparams == nil {
137-
return nil
73+
// Request gives access to the underlying HTTP request.
74+
func Request(ctx context.Context) *RequestData {
75+
r := ctx.Value(reqKey)
76+
if r != nil {
77+
return r.(*RequestData)
13878
}
139-
params := iparams.(url.Values)
140-
return params[name]
79+
return nil
14180
}
14281

143-
// GetNames returns all the querystring and URL parameter names.
144-
func (ctx *Context) GetNames() []string {
145-
iparams := ctx.Value(paramsKey)
146-
if iparams == nil {
147-
return nil
148-
}
149-
params := iparams.(url.Values)
150-
names := make([]string, len(params))
151-
i := 0
152-
for n := range params {
153-
names[i] = n
154-
i++
82+
// Response gives access to the underlying HTTP response.
83+
func Response(ctx context.Context) *ResponseData {
84+
r := ctx.Value(respKey)
85+
if r != nil {
86+
return r.(*ResponseData)
15587
}
156-
return names
88+
return nil
15789
}
15890

159-
// AllParams return all URL and querystring parameters.
160-
func (ctx *Context) AllParams() url.Values {
161-
iparams := ctx.Value(paramsKey)
162-
return iparams.(url.Values)
91+
// SetPayload initializes the unmarshaled request body value.
92+
func (r *RequestData) SetPayload(payload interface{}) {
93+
r.Payload = payload
16394
}
16495

165-
// RawPayload returns the deserialized request body or nil if body is empty.
166-
func (ctx *Context) RawPayload() interface{} {
167-
return ctx.Value(payloadKey)
96+
// SwitchResponseWriter overrides the underlying response writer. It returns the response
97+
// writer that was previously set.
98+
func (r *ResponseData) SwitchResponseWriter(rw http.ResponseWriter) http.ResponseWriter {
99+
rwo := r.ResponseWriter
100+
r.ResponseWriter = rw
101+
return rwo
168102
}
169103

170-
// RespondBytes writes the given HTTP status code and response body.
171-
// This method should only be called once per request.
172-
func (ctx *Context) RespondBytes(code int, body []byte) error {
173-
ctx.WriteHeader(code)
174-
if _, err := ctx.Write(body); err != nil {
175-
return err
176-
}
177-
return nil
104+
// Written returns true if the response was written.
105+
func (r *ResponseData) Written() bool {
106+
return r.Status != 0
178107
}
179108

180-
// Respond serializes the given body matching the request Accept header against the service
109+
// Send serializes the given body matching the request Accept header against the service
181110
// encoders. It uses the default service encoder if no match is found.
182-
func (ctx *Context) Respond(code int, body interface{}) error {
183-
ctx.WriteHeader(code)
184-
return ctx.Service().EncodeResponse(ctx, body)
111+
func (r *ResponseData) Send(code int, body interface{}) error {
112+
r.WriteHeader(code)
113+
return Request(r.ctx).Service.EncodeResponse(r.ctx, body)
185114
}
186115

187116
// BadRequest sends a HTTP response with status code 400 and the given error as body.
188-
func (ctx *Context) BadRequest(err *BadRequestError) error {
189-
return ctx.Respond(400, err.Error())
117+
func (r *ResponseData) BadRequest(err *BadRequestError) error {
118+
return r.Send(400, err.Error())
190119
}
191120

192121
// Bug sends a HTTP response with status code 500 and the given body.
193122
// The body can be set using a format and substituted values a la fmt.Printf.
194-
func (ctx *Context) Bug(format string, a ...interface{}) error {
123+
func (r *ResponseData) Bug(format string, a ...interface{}) error {
195124
body := fmt.Sprintf(format, a...)
196-
return ctx.Respond(500, body)
125+
return r.Send(500, body)
197126
}
198127

199-
// Header returns the response header. It implements the http.ResponseWriter interface.
200-
func (ctx *Context) Header() http.Header {
201-
rw := ctx.Value(respKey)
202-
if rw != nil {
203-
return rw.(http.ResponseWriter).Header()
204-
}
205-
return nil
206-
}
207-
208-
// WriteHeader writes the HTTP status code to the response. It implements the
209-
// http.ResponseWriter interface.
210-
func (ctx *Context) WriteHeader(code int) {
211-
rw := ctx.Value(respKey)
212-
if rw != nil {
213-
ctx.Context = context.WithValue(ctx.Context, respStatusKey, code)
214-
rw.(http.ResponseWriter).WriteHeader(code)
215-
}
128+
// WriteHeader records the response status code and calls the underlying writer.
129+
func (r *ResponseData) WriteHeader(status int) {
130+
r.Status = status
131+
r.ResponseWriter.WriteHeader(status)
216132
}
217133

218-
// Write writes the HTTP response body. It implements the http.ResponseWriter interface.
219-
func (ctx *Context) Write(body []byte) (int, error) {
220-
rw := ctx.Value(respKey)
221-
if rw != nil {
222-
ctx.Context = context.WithValue(ctx.Context, respLenKey, ctx.ResponseLength()+len(body))
223-
return rw.(http.ResponseWriter).Write(body)
224-
}
225-
return 0, fmt.Errorf("response writer not initialized")
134+
// Write records the amount of data written and calls the underlying writer.
135+
func (r *ResponseData) Write(b []byte) (int, error) {
136+
r.Len += len(b)
137+
return r.ResponseWriter.Write(b)
226138
}

context_test.go

-3
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,14 @@ import (
1111
. "github.com/onsi/ginkgo"
1212
. "github.com/onsi/gomega"
1313
"golang.org/x/net/context"
14-
"gopkg.in/inconshreveable/log15.v2"
1514
)
1615

1716
var _ = Describe("Context", func() {
18-
var logger log15.Logger
1917
var ctx *goa.Context
2018

2119
BeforeEach(func() {
2220
gctx := context.Background()
2321
ctx = goa.NewContext(gctx, goa.New("test"), nil, nil, nil)
24-
ctx.Logger = logger
2522
})
2623

2724
Describe("SetValue", func() {

encoding.go

+9-8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"io"
99
"mime"
1010
"sync"
11+
12+
"golang.org/x/net/context"
1113
)
1214

1315
type (
@@ -71,14 +73,13 @@ type (
7173

7274
// DecodeRequest retrives the request body and `Content-Type` header and uses Decode
7375
// to unmarshal into the provided `interface{}`
74-
func (ver *version) DecodeRequest(ctx *Context, v interface{}) error {
75-
body := ctx.Request().Body
76-
contentType := ctx.Request().Header.Get("Content-Type")
76+
func (ver *version) DecodeRequest(ctx context.Context, v interface{}) error {
77+
body := Request(ctx).Body
78+
contentType := Request(ctx).Header.Get("Content-Type")
7779
defer body.Close()
7880

7981
if err := ver.Decode(v, body, contentType); err != nil {
80-
ctx.Error(err.Error(), "ContentType", contentType)
81-
return err
82+
return fmt.Errorf("failed to decode request body with content type %#v: %s", contentType, err)
8283
}
8384

8485
return nil
@@ -175,8 +176,8 @@ func (p *decoderPool) Put(d Decoder) {
175176

176177
// EncodeResponse uses registered Encoders to marshal the response body based on the request
177178
// `Accept` header and writes it to the http.ResponseWriter
178-
func (ver *version) EncodeResponse(ctx *Context, v interface{}) error {
179-
accept := ctx.Request().Header.Get("Accept")
179+
func (ver *version) EncodeResponse(ctx context.Context, v interface{}) error {
180+
accept := Request(ctx).Header.Get("Accept")
180181
if accept == "" {
181182
accept = "*/*"
182183
}
@@ -196,7 +197,7 @@ func (ver *version) EncodeResponse(ctx *Context, v interface{}) error {
196197
}
197198

198199
// the encoderPool will handle whether or not a pool is actually in use
199-
encoder := p.Get(ctx)
200+
encoder := p.Get(Response(ctx))
200201
if err := encoder.Encode(v); err != nil {
201202
// TODO: log out error details
202203
return err

0 commit comments

Comments
 (0)