github.com/influx6/npkg@v0.8.8/nhttp/context.go (about)

     1  package nhttp
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"encoding/xml"
     8  	"errors"
     9  	"fmt"
    10  	htemplate "html/template"
    11  	"io"
    12  	"log"
    13  	"mime/multipart"
    14  	"net"
    15  	"net/http"
    16  	"net/url"
    17  	"os"
    18  	"path/filepath"
    19  	"strings"
    20  	"text/template"
    21  
    22  	"github.com/influx6/npkg/nerror"
    23  	"github.com/influx6/npkg/njson"
    24  
    25  	"github.com/influx6/npkg/nxid"
    26  )
    27  
    28  const (
    29  	defaultMemory = 64 << 20 // 64 MB
    30  )
    31  
    32  // Render defines a giving type which exposes a Render method for
    33  // rendering a custom output from a provided input string and bind
    34  // object.
    35  type Render interface {
    36  	Render(io.Writer, string, interface{}) error
    37  }
    38  
    39  // Options defines a function type which receives a Ctx pointer and
    40  // sets/modifiers it's internal state values.
    41  type Options func(*Ctx)
    42  
    43  // Apply applies giving options against Ctx instance returning context again.
    44  func Apply(c *Ctx, ops ...Options) *Ctx {
    45  	for _, op := range ops {
    46  		op(c)
    47  	}
    48  
    49  	return c
    50  }
    51  
    52  // SetID sets the id of the giving context.
    53  func SetID(id nxid.ID) Options {
    54  	return func(c *Ctx) {
    55  		c.id = id
    56  	}
    57  }
    58  
    59  // SetMultipartFormSize sets the expected size for any multipart data.
    60  func SetMultipartFormSize(size int64) Options {
    61  	return func(c *Ctx) {
    62  		c.multipartFormSize = size
    63  	}
    64  }
    65  
    66  // SetPath sets the path of the giving context.
    67  func SetPath(p string) Options {
    68  	return func(c *Ctx) {
    69  		c.path = p
    70  	}
    71  }
    72  
    73  // SetRenderer will returns a function to set the render used by a giving context.
    74  func SetRenderer(r Render) Options {
    75  	return func(c *Ctx) {
    76  		c.render = r
    77  	}
    78  }
    79  
    80  // SetResponseWriter returns a option function to set the response of a Ctx.
    81  func SetResponseWriter(w http.ResponseWriter, beforeFuncs ...func()) Options {
    82  	return func(c *Ctx) {
    83  		c.response = &Response{
    84  			beforeFuncs: beforeFuncs,
    85  			Writer:      w,
    86  		}
    87  	}
    88  }
    89  
    90  // SetResponse returns a option function to set the response of a Ctx.
    91  func SetResponse(r *Response) Options {
    92  	return func(c *Ctx) {
    93  		c.response = r
    94  	}
    95  }
    96  
    97  // SetRequest returns a option function to set the request of a Ctx.
    98  func SetRequest(r *http.Request) Options {
    99  	return func(c *Ctx) {
   100  		c.request = r
   101  		c.ctx = r.Context()
   102  		if err := c.InitForms(); err != nil {
   103  			var wrapErr = nerror.WrapOnly(err)
   104  			log.Printf("Failed to initialize forms: %+q", wrapErr)
   105  		}
   106  	}
   107  }
   108  
   109  // SetNotFound will return a function to set the NotFound handler for a giving context.
   110  func SetNotFound(r ContextHandler) Options {
   111  	return func(c *Ctx) {
   112  		c.notfoundHandler = r
   113  	}
   114  }
   115  
   116  //=========================================================================================
   117  
   118  // Ctx defines a http related context object for a request session
   119  // which is to be served.
   120  type Ctx struct {
   121  	ctx context.Context
   122  
   123  	multipartFormSize int64
   124  	id                nxid.ID
   125  	path              string
   126  	render            Render
   127  	response          *Response
   128  	query             url.Values
   129  	request           *http.Request
   130  	flash             map[string][]string
   131  	params            map[string]string
   132  	notfoundHandler   ContextHandler
   133  }
   134  
   135  // NewContext returns a new Ctx with the Options slice applied.
   136  func NewContext(ops ...Options) *Ctx {
   137  	c := &Ctx{
   138  		id:     nxid.New(),
   139  		flash:  map[string][]string{},
   140  		params: map[string]string{},
   141  	}
   142  
   143  	if c.multipartFormSize <= 0 {
   144  		c.multipartFormSize = defaultMemory
   145  	}
   146  
   147  	for _, op := range ops {
   148  		if op == nil {
   149  			continue
   150  		}
   151  
   152  		op(c)
   153  	}
   154  
   155  	return c
   156  }
   157  
   158  // ID returns the unique id of giving request context.
   159  func (c *Ctx) ID() nxid.ID {
   160  	return c.id
   161  }
   162  
   163  // Context returns the underline context.ctx for the request.
   164  func (c *Ctx) Context() context.Context {
   165  	return c.ctx
   166  }
   167  
   168  // Header returns the header associated with the giving request.
   169  func (c *Ctx) Header() http.Header {
   170  	return c.request.Header
   171  }
   172  
   173  // GetHeader returns associated value of key from request headers.
   174  func (c *Ctx) GetHeader(key string) string {
   175  	if c.request == nil {
   176  		return ""
   177  	}
   178  
   179  	return c.request.Header.Get(key)
   180  }
   181  
   182  // AddHeader adds te value into the giving key into the response object header.
   183  func (c *Ctx) AddHeader(key string, value string) {
   184  	if c.response == nil {
   185  		return
   186  	}
   187  
   188  	c.response.Header().Add(key, value)
   189  }
   190  
   191  // AddParam adds a new parameter value into the Ctx.
   192  //
   193  // This is not safe for concurrent use.
   194  func (c *Ctx) AddParam(key string, value string) {
   195  	c.params[key] = value
   196  }
   197  
   198  // AddForm adds a new form value into the Ctx.
   199  //
   200  // This is not safe for concurrent use.
   201  func (c *Ctx) Param(key string) string {
   202  	return c.params[key]
   203  }
   204  
   205  // SetHeader sets te key-value pair into the response object header.
   206  func (c *Ctx) SetHeader(key string, value string) {
   207  	if c.response == nil {
   208  		return
   209  	}
   210  
   211  	c.response.Header().Set(key, value)
   212  }
   213  
   214  // HasHeader returns true/false if string.Contains validate giving header key
   215  // has value within string of the request header.
   216  // if value is an empty string, then method only validates that you
   217  // have key in headers.
   218  func (c *Ctx) HasHeader(key string, value string) bool {
   219  	if c.request == nil {
   220  		return false
   221  	}
   222  
   223  	if value == "" {
   224  		return c.request.Header.Get(key) != ""
   225  	}
   226  
   227  	return strings.Contains(c.request.Header.Get(key), value)
   228  }
   229  
   230  // Request returns the associated request.
   231  func (c *Ctx) Request() *http.Request {
   232  	return c.request
   233  }
   234  
   235  // Body returns the associated io.ReadCloser which is the body of the Request.
   236  func (c *Ctx) Body() io.ReadCloser {
   237  	return c.request.Body
   238  }
   239  
   240  // Response returns the associated response object for this context.
   241  func (c *Ctx) Response() *Response {
   242  	return c.response
   243  }
   244  
   245  // IsTLS returns true/false if the giving reqest is a tls connection.
   246  func (c *Ctx) IsTLS() bool {
   247  	return c.request.TLS != nil
   248  }
   249  
   250  // IsWebSocket returns true/false if the giving reqest is a websocket connection.
   251  func (c *Ctx) IsWebSocket() bool {
   252  	upgrade := c.request.Header.Get(HeaderUpgrade)
   253  	return upgrade == "websocket" || upgrade == "Websocket"
   254  }
   255  
   256  // Scheme attempts to return the exact url scheme of the request.
   257  func (c *Ctx) Scheme() string {
   258  	// Can't use `r.Request.URL.Scheme`
   259  	// See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0
   260  	if c.IsTLS() {
   261  		return "https"
   262  	}
   263  	if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" {
   264  		return scheme
   265  	}
   266  	if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" {
   267  		return scheme
   268  	}
   269  	if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" {
   270  		return "https"
   271  	}
   272  	if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" {
   273  		return scheme
   274  	}
   275  	return "http"
   276  }
   277  
   278  // RealIP attempts to return the ip of the giving request.
   279  func (c *Ctx) RealIP() string {
   280  	ra := c.request.RemoteAddr
   281  	if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
   282  		ra = strings.Split(ip, ", ")[0]
   283  	} else if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
   284  		ra = ip
   285  	} else {
   286  		ra, _, _ = net.SplitHostPort(ra)
   287  	}
   288  	return ra
   289  }
   290  
   291  // Path returns the request path associated with the context.
   292  func (c *Ctx) Path() string {
   293  	if c.path == "" && c.request != nil {
   294  		c.path = c.request.URL.Path
   295  	}
   296  
   297  	return c.path
   298  }
   299  
   300  // QueryParam finds the giving value for the giving name in the querie set.
   301  func (c *Ctx) QueryParam(name string) string {
   302  	if c.query == nil {
   303  		c.query = c.request.URL.Query()
   304  	}
   305  
   306  	return c.query.Get(name)
   307  }
   308  
   309  // QueryParams returns the context url.Values object.
   310  func (c *Ctx) QueryParams() url.Values {
   311  	if c.query == nil {
   312  		c.query = c.request.URL.Query()
   313  	}
   314  	return c.query
   315  }
   316  
   317  // QueryString returns the raw query portion of the request path.
   318  func (c *Ctx) QueryString() string {
   319  	return c.request.URL.RawQuery
   320  }
   321  
   322  // Form returns the url.Values of the giving request.
   323  func (c *Ctx) Form() url.Values {
   324  	return c.request.Form
   325  }
   326  
   327  // FormValue returns the value of the giving item from the form fields.
   328  func (c *Ctx) FormValue(name string) string {
   329  	return c.request.FormValue(name)
   330  }
   331  
   332  // FormParams returns a url.Values which contains the parse form values for
   333  // multipart or wwww-urlencoded forms.
   334  func (c *Ctx) FormParams() (url.Values, error) {
   335  	if strings.HasPrefix(c.request.Header.Get(HeaderContentType), MIMEMultipartForm) {
   336  		if err := c.request.ParseMultipartForm(c.multipartFormSize); err != nil {
   337  			return nil, err
   338  		}
   339  	} else {
   340  		if err := c.request.ParseForm(); err != nil {
   341  			return nil, err
   342  		}
   343  	}
   344  	return c.request.Form, nil
   345  }
   346  
   347  // FormFile returns the giving FileHeader for the giving name.
   348  func (c *Ctx) FormFile(name string) (*multipart.FileHeader, error) {
   349  	_, fh, err := c.request.FormFile(name)
   350  	return fh, err
   351  }
   352  
   353  // MultipartForm returns the multipart form of the giving request if its a multipart
   354  // request.
   355  func (c *Ctx) MultipartForm() (*multipart.Form, error) {
   356  	err := c.request.ParseMultipartForm(defaultMemory)
   357  	return c.request.MultipartForm, err
   358  }
   359  
   360  // Cookie returns the associated cookie by the giving name.
   361  func (c *Ctx) Cookie(name string) (*http.Cookie, error) {
   362  	return c.request.Cookie(name)
   363  }
   364  
   365  // SetCookie sets the cookie into the response object.
   366  func (c *Ctx) SetCookie(cookie *http.Cookie) {
   367  	http.SetCookie(c.response, cookie)
   368  }
   369  
   370  // Cookies returns the associated cookies slice of the http request.
   371  func (c *Ctx) Cookies() []*http.Cookie {
   372  	return c.request.Cookies()
   373  }
   374  
   375  // ErrNoRenderInitiated defines the error returned when a renderer is not set
   376  // but Ctx.Render() is called.
   377  var ErrNoRenderInitiated = errors.New("Renderer was not set or is uninitiated")
   378  
   379  // Render renders the giving string with data binding using the provided Render
   380  // of the context.
   381  func (c *Ctx) Render(code int, tmpl string, data interface{}) (err error) {
   382  	if c.render == nil {
   383  		return ErrNoRenderInitiated
   384  	}
   385  
   386  	buf := new(bytes.Buffer)
   387  
   388  	if err = c.render.Render(buf, tmpl, data); err != nil {
   389  		return
   390  	}
   391  
   392  	return c.HTMLBlob(code, buf.Bytes())
   393  }
   394  
   395  // Template renders provided template.Template object into the response object.
   396  func (c *Ctx) Template(code int, tmpl *template.Template, data interface{}) error {
   397  	c.Status(code)
   398  	return tmpl.Funcs(TextContextFunctions(c)).Execute(c.response, data)
   399  }
   400  
   401  // HTMLTemplate renders provided template.Template object into the response object.
   402  func (c *Ctx) HTMLTemplate(code int, tmpl *htemplate.Template, data interface{}) error {
   403  	c.Status(code)
   404  	return tmpl.Funcs(HTMLContextFunctions(c)).Execute(c.response, data)
   405  }
   406  
   407  // HTML renders giving html into response.
   408  func (c *Ctx) HTML(code int, html string) (err error) {
   409  	return c.HTMLBlob(code, []byte(html))
   410  }
   411  
   412  // HTMLBlob renders giving html into response.
   413  func (c *Ctx) HTMLBlob(code int, b []byte) (err error) {
   414  	return c.Blob(code, MIMETextHTMLCharsetUTF8, b)
   415  }
   416  
   417  // Error renders giving error response into response.
   418  func (c *Ctx) Error(statusCode int, errorCode string, message string, err error) error {
   419  	c.response.Header().Set(HeaderContentType, MIMEApplicationJSONCharsetUTF8)
   420  	return JSONError(c.Response(), statusCode, errorCode, message, err)
   421  }
   422  
   423  // String renders giving string into response.
   424  func (c *Ctx) String(code int, s string) (err error) {
   425  	return c.Blob(code, MIMETextPlainCharsetUTF8, []byte(s))
   426  }
   427  
   428  // NJSON renders njson object as json response.
   429  func (c *Ctx) NJSON(code int, data *njson.JSON) (err error) {
   430  	c.response.Header().Set(HeaderContentType, MIMEApplicationJSON)
   431  	c.response.WriteHeader(code)
   432  	_, err = data.WriteTo(c.response)
   433  	return
   434  }
   435  
   436  // JSON renders giving json data into response.
   437  func (c *Ctx) JSON(code int, i interface{}) (err error) {
   438  	_, pretty := c.QueryParams()["pretty"]
   439  	if pretty {
   440  		return c.JSONPretty(code, i, "  ")
   441  	}
   442  	b, err := json.Marshal(i)
   443  	if err != nil {
   444  		return
   445  	}
   446  	return c.JSONBlob(code, b)
   447  }
   448  
   449  // JSONPretty renders giving json data as indented into response.
   450  func (c *Ctx) JSONPretty(code int, i interface{}, indent string) (err error) {
   451  	b, err := json.MarshalIndent(i, "", indent)
   452  	if err != nil {
   453  		return
   454  	}
   455  	return c.JSONBlob(code, b)
   456  }
   457  
   458  // JSONBlob renders giving json data into response with proper mime type.
   459  func (c *Ctx) JSONBlob(code int, b []byte) (err error) {
   460  	return c.Blob(code, MIMEApplicationJSONCharsetUTF8, b)
   461  }
   462  
   463  // JSONP renders giving jsonp as response with proper mime type.
   464  func (c *Ctx) JSONP(code int, callback string, i interface{}) (err error) {
   465  	b, err := json.Marshal(i)
   466  	if err != nil {
   467  		return
   468  	}
   469  	return c.JSONPBlob(code, callback, b)
   470  }
   471  
   472  // JSONPBlob renders giving jsonp as response with proper mime type.
   473  func (c *Ctx) JSONPBlob(code int, callback string, b []byte) (err error) {
   474  	c.response.Header().Set(HeaderContentType, MIMEApplicationJavaScriptCharsetUTF8)
   475  	c.response.WriteHeader(code)
   476  	if _, err = c.response.Write([]byte(callback + "(")); err != nil {
   477  		return
   478  	}
   479  	if _, err = c.response.Write(b); err != nil {
   480  		return
   481  	}
   482  	_, err = c.response.Write([]byte(");"))
   483  	return
   484  }
   485  
   486  // XML renders giving xml as response with proper mime type.
   487  func (c *Ctx) XML(code int, i interface{}) (err error) {
   488  	_, pretty := c.QueryParams()["pretty"]
   489  	if pretty {
   490  		return c.XMLPretty(code, i, "  ")
   491  	}
   492  	b, err := xml.Marshal(i)
   493  	if err != nil {
   494  		return
   495  	}
   496  	return c.XMLBlob(code, b)
   497  }
   498  
   499  // XMLPretty renders giving xml as indent as response with proper mime type.
   500  func (c *Ctx) XMLPretty(code int, i interface{}, indent string) (err error) {
   501  	b, err := xml.MarshalIndent(i, "", indent)
   502  	if err != nil {
   503  		return
   504  	}
   505  	return c.XMLBlob(code, b)
   506  }
   507  
   508  // XMLBlob renders giving xml as response with proper mime type.
   509  func (c *Ctx) XMLBlob(code int, b []byte) (err error) {
   510  	c.response.Header().Set(HeaderContentType, MIMEApplicationXMLCharsetUTF8)
   511  	c.response.WriteHeader(code)
   512  	if _, err = c.response.Write([]byte(xml.Header)); err != nil {
   513  		return
   514  	}
   515  	_, err = c.response.Write(b)
   516  	return
   517  }
   518  
   519  // Blob write giving byte slice as response with proper mime type.
   520  func (c *Ctx) Blob(code int, contentType string, b []byte) (err error) {
   521  	c.response.Header().Set(HeaderContentType, contentType)
   522  	c.response.WriteHeader(code)
   523  	_, err = c.response.Write(b)
   524  	return
   525  }
   526  
   527  // Stream copies giving io.Readers content into response.
   528  func (c *Ctx) Stream(code int, contentType string, r io.Reader) (err error) {
   529  	c.response.Header().Set(HeaderContentType, contentType)
   530  	c.response.WriteHeader(code)
   531  	_, err = io.Copy(c.response, r)
   532  	return
   533  }
   534  
   535  // File streams file content into response.
   536  func (c *Ctx) File(file string) (err error) {
   537  	f, err := os.Open(file)
   538  	if err != nil {
   539  		return err
   540  	}
   541  
   542  	defer f.Close()
   543  
   544  	fi, _ := f.Stat()
   545  	if fi.IsDir() {
   546  		file = filepath.Join(file, "index.html")
   547  		f, err = os.Open(file)
   548  		if err != nil {
   549  			return
   550  		}
   551  
   552  		defer f.Close()
   553  		if fi, err = f.Stat(); err != nil {
   554  			return
   555  		}
   556  	}
   557  
   558  	http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f)
   559  	return
   560  }
   561  
   562  // Attachment attempts to attach giving file details.
   563  func (c *Ctx) Attachment(file, name string) (err error) {
   564  	return c.contentDisposition(file, name, "attachment")
   565  }
   566  
   567  // Inline attempts to inline file content.
   568  func (c *Ctx) Inline(file, name string) (err error) {
   569  	return c.contentDisposition(file, name, "inline")
   570  }
   571  
   572  func (c *Ctx) contentDisposition(file, name, dispositionType string) (err error) {
   573  	c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%s", dispositionType, name))
   574  	c.File(file)
   575  	return
   576  }
   577  
   578  // SetFlash sets giving message/messages into the slice bucket of the
   579  // given name list.
   580  func (c *Ctx) SetFlash(name string, message string) {
   581  	c.flash[name] = append(c.flash[name], message)
   582  }
   583  
   584  // ClearFlashMessages clears all available message items within
   585  // the flash map.
   586  func (c *Ctx) ClearFlashMessages() {
   587  	c.flash = make(map[string][]string)
   588  }
   589  
   590  // FlashMessages returns available map of all flash messages.
   591  // A copy is sent not the context currently used instance.
   592  func (c *Ctx) FlashMessages() map[string][]string {
   593  	copy := make(map[string][]string)
   594  	for name, messages := range c.flash {
   595  		copy[name] = append([]string{}, messages...)
   596  	}
   597  	return copy
   598  }
   599  
   600  // ClearFlash removes all available message items from the context flash message
   601  // map.
   602  func (c *Ctx) ClearFlash(name string) {
   603  	if _, ok := c.flash[name]; ok {
   604  		c.flash[name] = nil
   605  	}
   606  }
   607  
   608  // Flash returns an associated slice of messages/string, for giving
   609  // flash name/key.
   610  func (c *Ctx) Flash(name string) []string {
   611  	messages := c.flash[name]
   612  	return messages
   613  }
   614  
   615  // ModContext executes provided function against current context
   616  // modifying the current context with the returned and updating
   617  // underlying request with new context Ctx.
   618  //
   619  // It is not safe for concurrent use.
   620  func (c *Ctx) ModContext(modder func(ctx context.Context) context.Context) {
   621  	if c.ctx == nil {
   622  		return
   623  	}
   624  
   625  	var newCtx = modder(c.ctx)
   626  	c.request = c.request.WithContext(newCtx)
   627  	c.ctx = c.request.Context()
   628  }
   629  
   630  // NotFound writes calls the giving response against the NotFound handler
   631  // if present, else uses a http.StatusMovedPermanently status code.
   632  func (c *Ctx) NotFound() error {
   633  	if c.notfoundHandler != nil {
   634  		return c.notfoundHandler(c)
   635  	}
   636  
   637  	c.response.WriteHeader(http.StatusMovedPermanently)
   638  	return nil
   639  }
   640  
   641  // Status writes status code without writing content to response.
   642  func (c *Ctx) Status(code int) {
   643  	c.response.WriteHeader(code)
   644  }
   645  
   646  // NoContent writes status code without writing content to response.
   647  func (c *Ctx) NoContent(code int) error {
   648  	c.response.WriteHeader(code)
   649  	return nil
   650  }
   651  
   652  // ErrInvalidRedirectCode is error returned when redirect code is wrong.
   653  var ErrInvalidRedirectCode = errors.New("Invalid redirect code")
   654  
   655  // Redirect redirects context response.
   656  func (c *Ctx) Redirect(code int, url string) error {
   657  	if code < 300 || code > 308 {
   658  		return ErrInvalidRedirectCode
   659  	}
   660  
   661  	c.response.Header().Set(HeaderLocation, url)
   662  	c.response.WriteHeader(code)
   663  	return nil
   664  }
   665  
   666  // InitForms will call the appropriate function to parse the necessary form values
   667  // within the giving request context.
   668  func (c *Ctx) InitForms() error {
   669  	if c.request == nil {
   670  		return nil
   671  	}
   672  
   673  	if _, err := c.FormParams(); err != nil {
   674  		return err
   675  	}
   676  	return nil
   677  }
   678  
   679  // Reset resets context internal fields
   680  func (c *Ctx) Reset(r *http.Request, w http.ResponseWriter) error {
   681  	if r == nil && w == nil {
   682  		c.request = nil
   683  		c.response = nil
   684  		c.query = nil
   685  		c.notfoundHandler = nil
   686  		c.params = nil
   687  		c.flash = nil
   688  		c.ctx = nil
   689  		return nil
   690  	}
   691  
   692  	c.request = r
   693  	c.query = nil
   694  	c.id = nxid.New()
   695  	c.ctx = r.Context()
   696  	c.notfoundHandler = nil
   697  	c.params = map[string]string{}
   698  	c.flash = map[string][]string{}
   699  
   700  	if c.multipartFormSize <= 0 {
   701  		c.multipartFormSize = defaultMemory
   702  	}
   703  
   704  	c.request = r
   705  	c.response = &Response{Writer: w}
   706  	return c.InitForms()
   707  }