github.com/fedir/buffalo@v0.11.1/default_context.go (about) 1 package buffalo 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "net/http" 9 "net/url" 10 "runtime" 11 "sort" 12 "strings" 13 "time" 14 15 "github.com/gobuffalo/buffalo/binding" 16 "github.com/gobuffalo/buffalo/render" 17 "github.com/gobuffalo/pop" 18 "github.com/gorilla/websocket" 19 "github.com/pkg/errors" 20 ) 21 22 // assert that DefaultContext is implementing Context 23 var _ Context = &DefaultContext{} 24 var _ context.Context = &DefaultContext{} 25 26 // DefaultContext is, as its name implies, a default 27 // implementation of the Context interface. 28 type DefaultContext struct { 29 context.Context 30 response http.ResponseWriter 31 request *http.Request 32 params url.Values 33 logger Logger 34 session *Session 35 contentType string 36 data map[string]interface{} 37 flash *Flash 38 } 39 40 // Response returns the original Response for the request. 41 func (d *DefaultContext) Response() http.ResponseWriter { 42 return d.response 43 } 44 45 // Request returns the original Request. 46 func (d *DefaultContext) Request() *http.Request { 47 return d.request 48 } 49 50 // Params returns all of the parameters for the request, 51 // including both named params and query string parameters. 52 func (d *DefaultContext) Params() ParamValues { 53 return d.params 54 } 55 56 // Logger returns the Logger for this context. 57 func (d *DefaultContext) Logger() Logger { 58 return d.logger 59 } 60 61 // Param returns a param, either named or query string, 62 // based on the key. 63 func (d *DefaultContext) Param(key string) string { 64 return d.Params().Get(key) 65 } 66 67 // Set a value onto the Context. Any value set onto the Context 68 // will be automatically available in templates. 69 func (d *DefaultContext) Set(key string, value interface{}) { 70 d.data[key] = value 71 } 72 73 // Value that has previously stored on the context. 74 func (d *DefaultContext) Value(key interface{}) interface{} { 75 if k, ok := key.(string); ok { 76 if v, ok := d.data[k]; ok { 77 return v 78 } 79 } 80 return d.Context.Value(key) 81 } 82 83 // Session for the associated Request. 84 func (d *DefaultContext) Session() *Session { 85 return d.session 86 } 87 88 // Cookies for the associated request and response. 89 func (d *DefaultContext) Cookies() *Cookies { 90 return &Cookies{d.request, d.response} 91 } 92 93 // Flash messages for the associated Request. 94 func (d *DefaultContext) Flash() *Flash { 95 return d.flash 96 } 97 98 // Render a status code and render.Renderer to the associated Response. 99 // The request parameters will be made available to the render.Renderer 100 // "{{.params}}". Any values set onto the Context will also automatically 101 // be made available to the render.Renderer. To render "no content" pass 102 // in a nil render.Renderer. 103 func (d *DefaultContext) Render(status int, rr render.Renderer) error { 104 start := time.Now() 105 defer func() { 106 d.LogField("render", time.Since(start)) 107 }() 108 if rr != nil { 109 data := d.data 110 pp := map[string]string{} 111 for k, v := range d.params { 112 pp[k] = v[0] 113 } 114 data["params"] = pp 115 data["flash"] = d.Flash().data 116 data["session"] = d.Session() 117 data["request"] = d.Request() 118 data["status"] = status 119 bb := &bytes.Buffer{} 120 121 err := rr.Render(bb, data) 122 if err != nil { 123 if er, ok := errors.Cause(err).(render.ErrRedirect); ok { 124 return d.Redirect(er.Status, er.URL) 125 } 126 return HTTPError{Status: 500, Cause: errors.WithStack(err)} 127 } 128 129 if d.Session() != nil { 130 d.Flash().Clear() 131 d.Flash().persist(d.Session()) 132 } 133 134 d.Response().Header().Set("Content-Type", rr.ContentType()) 135 if p, ok := data["pagination"].(*pop.Paginator); ok { 136 d.Response().Header().Set("X-Pagination", p.String()) 137 } 138 d.Response().WriteHeader(status) 139 _, err = io.Copy(d.Response(), bb) 140 if err != nil { 141 return HTTPError{Status: 500, Cause: errors.WithStack(err)} 142 } 143 144 return nil 145 } 146 d.Response().WriteHeader(status) 147 return nil 148 } 149 150 // Bind the interface to the request.Body. The type of binding 151 // is dependent on the "Content-Type" for the request. If the type 152 // is "application/json" it will use "json.NewDecoder". If the type 153 // is "application/xml" it will use "xml.NewDecoder". See the 154 // github.com/gobuffalo/buffalo/binding package for more details. 155 func (d *DefaultContext) Bind(value interface{}) error { 156 return binding.Exec(d.Request(), value) 157 } 158 159 // LogField adds the key/value pair onto the Logger to be printed out 160 // as part of the request logging. This allows you to easily add things 161 // like metrics (think DB times) to your request. 162 func (d *DefaultContext) LogField(key string, value interface{}) { 163 d.logger = d.logger.WithField(key, value) 164 } 165 166 // LogFields adds the key/value pairs onto the Logger to be printed out 167 // as part of the request logging. This allows you to easily add things 168 // like metrics (think DB times) to your request. 169 func (d *DefaultContext) LogFields(values map[string]interface{}) { 170 d.logger = d.logger.WithFields(values) 171 } 172 173 func (d *DefaultContext) Error(status int, err error) error { 174 return HTTPError{Status: status, Cause: errors.WithStack(err)} 175 } 176 177 // Websocket is deprecated, and will be removed in v0.12.0. Use github.com/gorilla/websocket directly instead. 178 func (d *DefaultContext) Websocket() (*websocket.Conn, error) { 179 warningMsg := "Websocket is deprecated, and will be removed in v0.12.0. Use github.com/gorilla/websocket directly instead." 180 _, file, no, ok := runtime.Caller(1) 181 if ok { 182 warningMsg = fmt.Sprintf("%s Called from %s:%d", warningMsg, file, no) 183 } 184 return defaultUpgrader.Upgrade(d.Response(), d.Request(), nil) 185 } 186 187 // Redirect a request with the given status to the given URL. 188 func (d *DefaultContext) Redirect(status int, url string, args ...interface{}) error { 189 d.Flash().persist(d.Session()) 190 191 if len(args) > 0 { 192 url = fmt.Sprintf(url, args...) 193 } 194 http.Redirect(d.Response(), d.Request(), url, status) 195 return nil 196 } 197 198 // Data contains all the values set through Get/Set. 199 func (d *DefaultContext) Data() map[string]interface{} { 200 return d.data 201 } 202 203 func (d *DefaultContext) String() string { 204 bb := make([]string, 0, len(d.data)) 205 206 for k, v := range d.data { 207 if _, ok := v.(RouteHelperFunc); !ok { 208 bb = append(bb, fmt.Sprintf("%s: %s", k, v)) 209 } 210 } 211 sort.Strings(bb) 212 return strings.Join(bb, "\n\n") 213 } 214 215 // File returns an uploaded file by name, or an error 216 func (d *DefaultContext) File(name string) (binding.File, error) { 217 req := d.Request() 218 if err := req.ParseMultipartForm(5 * 1024 * 1024); err != nil { 219 return binding.File{}, err 220 } 221 f, h, err := req.FormFile(name) 222 bf := binding.File{ 223 File: f, 224 FileHeader: h, 225 } 226 if err != nil { 227 return bf, errors.WithStack(err) 228 } 229 return bf, nil 230 } 231 232 var defaultUpgrader = websocket.Upgrader{ 233 ReadBufferSize: 1024, 234 WriteBufferSize: 1024, 235 }