forked from go-aah/aah
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontext.go
253 lines (212 loc) · 7.1 KB
/
context.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
// Copyright (c) Jeevanandam M. (https://github.com/jeevatkm)
// go-aah/aah source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package aah
import (
"errors"
"net/url"
"reflect"
"strings"
"aahframework.org/ahttp.v0"
"aahframework.org/essentials.v0"
"aahframework.org/log.v0"
"aahframework.org/router.v0"
)
var (
ctxPtrType = reflect.TypeOf((*Context)(nil))
errTargetNotFound = errors.New("target not found")
)
type (
// Context type for aah framework, gets embedded in application controller.
Context struct {
// Req is HTTP request instance
Req *ahttp.Request
// Res is HTTP response writer compliant. It is highly recommended to use
// `Reply()` builder for composing response.
//
// Note: If you're using `cxt.Res` directly, don't forget to call
// `Reply().Done()` so that framework will not interfere with your
// response composition.
Res ahttp.ResponseWriter
controller string
action *MethodInfo
target interface{}
domain *router.Domain
route *router.Route
reply *Reply
viewArgs map[string]interface{}
abort bool
decorated bool
}
)
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Context methods
//___________________________________
// Reply method gives you control and convenient way to write
// a response effectively.
func (ctx *Context) Reply() *Reply {
return ctx.reply
}
// ViewArgs method returns aah framework and request related info that can be
// used in template or view rendering, etc.
func (ctx *Context) ViewArgs() map[string]interface{} {
return ctx.viewArgs
}
// AddViewArg method adds given key and value into `viewArgs`. These view args
// values accessible on templates. Chained call is possible.
func (ctx *Context) AddViewArg(key string, value interface{}) *Context {
ctx.viewArgs[key] = value
return ctx
}
// ReverseURL method returns the URL for given route name and args.
// See `Domain.ReverseURL` for more information.
func (ctx *Context) ReverseURL(routeName string, args ...interface{}) string {
return createReverseURL(ctx.Req.Host, routeName, nil, args...)
}
// ReverseURLm method returns the URL for given route name and key-value paris.
// See `Domain.ReverseURLm` for more information.
func (ctx *Context) ReverseURLm(routeName string, args map[string]interface{}) string {
return createReverseURL(ctx.Req.Host, routeName, args)
}
// Msg method returns the i18n value for given key otherwise empty string returned.
func (ctx *Context) Msg(key string, args ...interface{}) string {
return AppI18n().Lookup(ctx.Req.Locale, key, args...)
}
// Msgl method returns the i18n value for given local and key otherwise
// empty string returned.
func (ctx *Context) Msgl(locale *ahttp.Locale, key string, args ...interface{}) string {
return AppI18n().Lookup(locale, key, args...)
}
// Abort method sets the abort to true. It means framework will not proceed with
// next middleware, next interceptor or action based on context it being used.
// Contexts: 1) If it's called in the middleware, then middleware chain stops;
// framework starts processing response. 2) If it's called in Before interceptor
// then Before<Action> interceptor, mapped <Action>, After<Action> interceptor and
// After interceptor will not execute; framework starts processing response.
// 3) If it's called in Mapped <Action> then After<Action> interceptor and
// After interceptor will not execute; framework starts processing response.
func (ctx *Context) Abort() {
ctx.abort = true
}
// SetURL method is to set the request URL to change the behaviour of request
// routing. Ideal for URL rewrting. URL can be relative or absolute URL.
//
// Note: This method only takes effect on `OnRequest` server event.
func (ctx *Context) SetURL(pathURL string) {
if !ctx.decorated {
return
}
u, err := url.Parse(pathURL)
if err != nil {
log.Errorf("invalid URL provided: %s", err)
return
}
rawReq := ctx.Req.Raw
if !ess.IsStrEmpty(u.Host) {
log.Debugf("Host have been updated from '%s' to '%s'", ctx.Req.Host, u.Host)
rawReq.Host = u.Host
rawReq.URL.Host = u.Host
}
log.Debugf("URL path have been updated from '%s' to '%s'", ctx.Req.Path, u.Path)
rawReq.URL.Path = u.Path
// Update the context
ctx.Req.Host = rawReq.Host
ctx.Req.Path = rawReq.URL.Path
}
// SetMethod method is to set the request `Method` to change the behaviour
// of request routing. Ideal for URL rewrting.
//
// Note: This method only takes effect on `OnRequest` server event.
func (ctx *Context) SetMethod(method string) {
if !ctx.decorated {
return
}
method = strings.ToUpper(method)
if _, found := router.HTTPMethodActionMap[method]; !found {
log.Errorf("given method '%s' is not valid", method)
return
}
log.Debugf("Request method have been updated from '%s' to '%s'", ctx.Req.Method, method)
ctx.Req.Raw.Method = method
ctx.Req.Method = method
}
// Reset method resets context instance for reuse.
func (ctx *Context) Reset() {
ctx.Req = nil
ctx.Res = nil
ctx.target = nil
ctx.domain = nil
ctx.controller = ""
ctx.action = nil
ctx.reply = nil
ctx.viewArgs = nil
ctx.abort = false
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Context Unexported methods
//___________________________________
// setTarget method sets contoller, action, embedded context into
// controller.
func (ctx *Context) setTarget(route *router.Route) error {
controller := cRegistry.Lookup(route)
if controller == nil {
return errTargetNotFound
}
ctx.controller = controller.Name()
ctx.action = controller.FindMethod(route.Action)
if ctx.action == nil {
return errTargetNotFound
}
targetPtr := reflect.New(controller.Type)
target := targetPtr.Elem()
ctxv := reflect.ValueOf(ctx)
for _, index := range controller.EmbeddedIndexes {
target.FieldByIndex(index).Set(ctxv)
}
ctx.target = targetPtr.Interface()
return nil
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Unexported methods
//___________________________________
// findEmbeddedContext method does breadth-first search on struct anonymous
// field to find `aah.Context` index positions.
func findEmbeddedContext(controllerType reflect.Type) [][]int {
var indexes [][]int
type nodeType struct {
val reflect.Value
index []int
}
queue := []nodeType{{reflect.New(controllerType), []int{}}}
for len(queue) > 0 {
var (
node = queue[0]
elem = node.val
elemType = elem.Type()
)
if elemType.Kind() == reflect.Ptr {
elem = elem.Elem()
elemType = elem.Type()
}
queue = queue[1:]
if elemType.Kind() != reflect.Struct {
continue
}
for i := 0; i < elem.NumField(); i++ {
// skip non-anonymous fields
field := elemType.Field(i)
if !field.Anonymous {
continue
}
// If it's a `aah.Context`, record the field indexes
if field.Type == ctxPtrType {
indexes = append(indexes, append(node.index, i))
continue
}
fieldValue := elem.Field(i)
queue = append(queue,
nodeType{fieldValue, append(append([]int{}, node.index...), i)})
}
}
return indexes
}