1// Copyright 2014 The Macaron Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"): you may 4// not use this file except in compliance with the License. You may obtain 5// a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12// License for the specific language governing permissions and limitations 13// under the License. 14 15package macaron 16 17import ( 18 "encoding/json" 19 "html/template" 20 "net/http" 21 "net/url" 22 "reflect" 23 "strconv" 24 "strings" 25) 26 27// ContextInvoker is an inject.FastInvoker wrapper of func(ctx *Context). 28type ContextInvoker func(ctx *Context) 29 30// Invoke implements inject.FastInvoker which simplifies calls of `func(ctx *Context)` function. 31func (invoke ContextInvoker) Invoke(params []interface{}) ([]reflect.Value, error) { 32 invoke(params[0].(*Context)) 33 return nil, nil 34} 35 36// Context represents the runtime context of current request of Macaron instance. 37// It is the integration of most frequently used middlewares and helper methods. 38type Context struct { 39 Injector 40 handlers []Handler 41 index int 42 43 *Router 44 Req *http.Request 45 Resp ResponseWriter 46 template *template.Template 47} 48 49func (ctx *Context) handler() Handler { 50 if ctx.index < len(ctx.handlers) { 51 return ctx.handlers[ctx.index] 52 } 53 if ctx.index == len(ctx.handlers) { 54 return func() {} 55 } 56 panic("invalid index for context handler") 57} 58 59// Next runs the next handler in the context chain 60func (ctx *Context) Next() { 61 ctx.index++ 62 ctx.run() 63} 64 65func (ctx *Context) run() { 66 for ctx.index <= len(ctx.handlers) { 67 if _, err := ctx.Invoke(ctx.handler()); err != nil { 68 panic(err) 69 } 70 ctx.index++ 71 if ctx.Resp.Written() { 72 return 73 } 74 } 75} 76 77// RemoteAddr returns more real IP address. 78func (ctx *Context) RemoteAddr() string { 79 addr := ctx.Req.Header.Get("X-Real-IP") 80 81 if len(addr) == 0 { 82 // X-Forwarded-For may contain multiple IP addresses, separated by 83 // commas. 84 addr = strings.TrimSpace(strings.Split(ctx.Req.Header.Get("X-Forwarded-For"), ",")[0]) 85 } 86 87 if len(addr) == 0 { 88 addr = ctx.Req.RemoteAddr 89 if i := strings.LastIndex(addr, ":"); i > -1 { 90 addr = addr[:i] 91 } 92 } 93 return addr 94} 95 96const ( 97 headerContentType = "Content-Type" 98 contentTypeJSON = "application/json; charset=UTF-8" 99 contentTypeHTML = "text/html; charset=UTF-8" 100) 101 102// HTML renders the HTML with default template set. 103func (ctx *Context) HTML(status int, name string, data interface{}) { 104 ctx.Resp.Header().Set(headerContentType, contentTypeHTML) 105 ctx.Resp.WriteHeader(status) 106 if err := ctx.template.ExecuteTemplate(ctx.Resp, name, data); err != nil { 107 panic("Context.HTML:" + err.Error()) 108 } 109} 110 111func (ctx *Context) JSON(status int, data interface{}) { 112 ctx.Resp.Header().Set(headerContentType, contentTypeJSON) 113 ctx.Resp.WriteHeader(status) 114 enc := json.NewEncoder(ctx.Resp) 115 if Env != PROD { 116 enc.SetIndent("", " ") 117 } 118 if err := enc.Encode(data); err != nil { 119 panic("Context.JSON: " + err.Error()) 120 } 121} 122 123// Redirect sends a redirect response 124func (ctx *Context) Redirect(location string, status ...int) { 125 code := http.StatusFound 126 if len(status) == 1 { 127 code = status[0] 128 } 129 130 http.Redirect(ctx.Resp, ctx.Req, location, code) 131} 132 133// MaxMemory is the maximum amount of memory to use when parsing a multipart form. 134// Set this to whatever value you prefer; default is 10 MB. 135var MaxMemory = int64(1024 * 1024 * 10) 136 137func (ctx *Context) parseForm() { 138 if ctx.Req.Form != nil { 139 return 140 } 141 142 contentType := ctx.Req.Header.Get(headerContentType) 143 if (ctx.Req.Method == "POST" || ctx.Req.Method == "PUT") && 144 len(contentType) > 0 && strings.Contains(contentType, "multipart/form-data") { 145 _ = ctx.Req.ParseMultipartForm(MaxMemory) 146 } else { 147 _ = ctx.Req.ParseForm() 148 } 149} 150 151// Query querys form parameter. 152func (ctx *Context) Query(name string) string { 153 ctx.parseForm() 154 return ctx.Req.Form.Get(name) 155} 156 157// QueryStrings returns a list of results by given query name. 158func (ctx *Context) QueryStrings(name string) []string { 159 ctx.parseForm() 160 161 vals, ok := ctx.Req.Form[name] 162 if !ok { 163 return []string{} 164 } 165 return vals 166} 167 168// QueryBool returns query result in bool type. 169func (ctx *Context) QueryBool(name string) bool { 170 v, _ := strconv.ParseBool(ctx.Query(name)) 171 return v 172} 173 174// QueryInt returns query result in int type. 175func (ctx *Context) QueryInt(name string) int { 176 n, _ := strconv.Atoi(ctx.Query(name)) 177 return n 178} 179 180// QueryInt64 returns query result in int64 type. 181func (ctx *Context) QueryInt64(name string) int64 { 182 n, _ := strconv.ParseInt(ctx.Query(name), 10, 64) 183 return n 184} 185 186// ParamsInt64 returns params result in int64 type. 187// e.g. ctx.ParamsInt64(":uid") 188func (ctx *Context) ParamsInt64(name string) int64 { 189 n, _ := strconv.ParseInt(Params(ctx.Req)[name], 10, 64) 190 return n 191} 192 193// GetCookie returns given cookie value from request header. 194func (ctx *Context) GetCookie(name string) string { 195 cookie, err := ctx.Req.Cookie(name) 196 if err != nil { 197 return "" 198 } 199 val, _ := url.QueryUnescape(cookie.Value) 200 return val 201} 202