github.com/ashleymcnamara/buffalo@v0.8.0/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 "strconv" 12 "strings" 13 "time" 14 15 "github.com/gobuffalo/buffalo/render" 16 "github.com/gorilla/websocket" 17 "github.com/pkg/errors" 18 ) 19 20 // assert that DefaultContext is implementing Context 21 var _ Context = &DefaultContext{} 22 var _ context.Context = &DefaultContext{} 23 24 // DefaultContext is, as its name implies, a default 25 // implementation of the Context interface. 26 type DefaultContext struct { 27 context.Context 28 response http.ResponseWriter 29 request *http.Request 30 params url.Values 31 logger Logger 32 session *Session 33 contentType string 34 data map[string]interface{} 35 flash *Flash 36 } 37 38 // Response returns the original Response for the request. 39 func (d *DefaultContext) Response() http.ResponseWriter { 40 return d.response 41 } 42 43 // Request returns the original Request. 44 func (d *DefaultContext) Request() *http.Request { 45 return d.request 46 } 47 48 // Params returns all of the parameters for the request, 49 // including both named params and query string parameters. 50 // These parameters are automatically available in templates 51 // as "{{.params}}". 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 // ParamInt tries to convert the requested parameter to 68 // an int. It will return an error if there is a problem. 69 func (d *DefaultContext) ParamInt(key string) (int, error) { 70 k := d.Params().Get(key) 71 i, err := strconv.Atoi(k) 72 return i, errors.WithMessage(err, fmt.Sprintf("could not convert %s to an int", k)) 73 } 74 75 // Set a value onto the Context. Any value set onto the Context 76 // will be automatically available in templates. 77 func (d *DefaultContext) Set(key string, value interface{}) { 78 d.data[key] = value 79 } 80 81 // Get is deprecated. Please use Value instead. 82 func (d *DefaultContext) Get(key string) interface{} { 83 warningMsg := "Context#Get is deprecated. Please use Context#Value instead." 84 85 _, file, no, ok := runtime.Caller(1) 86 if ok { 87 warningMsg = fmt.Sprintf("Context#Get is deprecated. Please use Context#Value instead. Called from %s:%d", file, no) 88 } 89 90 d.Logger().Warn(warningMsg) 91 return d.Value(key) 92 } 93 94 // Value that has previously stored on the context. 95 func (d *DefaultContext) Value(key interface{}) interface{} { 96 if k, ok := key.(string); ok { 97 if v, ok := d.data[k]; ok { 98 return v 99 } 100 } 101 return d.Context.Value(key) 102 } 103 104 // Session for the associated Request. 105 func (d *DefaultContext) Session() *Session { 106 return d.session 107 } 108 109 // Flash messages for the associated Request. 110 func (d *DefaultContext) Flash() *Flash { 111 return d.flash 112 } 113 114 // Render a status code and render.Renderer to the associated Response. 115 // The request parameters will be made available to the render.Renderer 116 // "{{.params}}". Any values set onto the Context will also automatically 117 // be made available to the render.Renderer. To render "no content" pass 118 // in a nil render.Renderer. 119 func (d *DefaultContext) Render(status int, rr render.Renderer) error { 120 now := time.Now() 121 defer func() { 122 d.LogField("render", time.Now().Sub(now)) 123 }() 124 if rr != nil { 125 data := d.data 126 pp := map[string]string{} 127 for k, v := range d.params { 128 pp[k] = v[0] 129 } 130 data["params"] = pp 131 data["flash"] = d.Flash().data 132 bb := &bytes.Buffer{} 133 134 err := rr.Render(bb, data) 135 if err != nil { 136 return HTTPError{Status: 500, Cause: errors.WithStack(err)} 137 } 138 139 if d.Session() != nil { 140 d.Flash().Clear() 141 d.Flash().persist(d.Session()) 142 } 143 144 d.Response().Header().Set("Content-Type", rr.ContentType()) 145 d.Response().WriteHeader(status) 146 _, err = io.Copy(d.Response(), bb) 147 if err != nil { 148 return HTTPError{Status: 500, Cause: errors.WithStack(err)} 149 } 150 151 return nil 152 } 153 d.Response().WriteHeader(status) 154 return nil 155 } 156 157 // Bind the interface to the request.Body. The type of binding 158 // is dependent on the "Content-Type" for the request. If the type 159 // is "application/json" it will use "json.NewDecoder". If the type 160 // is "application/xml" it will use "xml.NewDecoder". The default 161 // binder is "http://www.gorillatoolkit.org/pkg/schema". 162 func (d *DefaultContext) Bind(value interface{}) error { 163 ct := strings.ToLower(d.Request().Header.Get("Content-Type")) 164 if ct != "" { 165 cts := strings.Split(ct, ";") 166 c := cts[0] 167 if b, ok := binders[strings.TrimSpace(c)]; ok { 168 return b(d.Request(), value) 169 } 170 return errors.Errorf("could not find a binder for %s", c) 171 } 172 return errors.New("blank content type") 173 } 174 175 // LogField adds the key/value pair onto the Logger to be printed out 176 // as part of the request logging. This allows you to easily add things 177 // like metrics (think DB times) to your request. 178 func (d *DefaultContext) LogField(key string, value interface{}) { 179 d.logger = d.logger.WithField(key, value) 180 } 181 182 // LogFields adds the key/value pairs onto the Logger to be printed out 183 // as part of the request logging. This allows you to easily add things 184 // like metrics (think DB times) to your request. 185 func (d *DefaultContext) LogFields(values map[string]interface{}) { 186 d.logger = d.logger.WithFields(values) 187 } 188 189 func (d *DefaultContext) Error(status int, err error) error { 190 return HTTPError{Status: status, Cause: errors.WithStack(err)} 191 } 192 193 // Websocket returns an upgraded github.com/gorilla/websocket.Conn 194 // that can then be used to work with websockets easily. 195 func (d *DefaultContext) Websocket() (*websocket.Conn, error) { 196 return defaultUpgrader.Upgrade(d.Response(), d.Request(), nil) 197 } 198 199 // Redirect a request with the given status to the given URL. 200 func (d *DefaultContext) Redirect(status int, url string, args ...interface{}) error { 201 d.Flash().persist(d.Session()) 202 203 http.Redirect(d.Response(), d.Request(), fmt.Sprintf(url, args...), status) 204 return nil 205 } 206 207 // Data contains all the values set through Get/Set. 208 func (d *DefaultContext) Data() map[string]interface{} { 209 return d.data 210 } 211 212 var defaultUpgrader = websocket.Upgrader{ 213 ReadBufferSize: 1024, 214 WriteBufferSize: 1024, 215 CheckOrigin: func(r *http.Request) bool { return true }, 216 }