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