github.com/corylanou/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  }