mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 18:51:03 +01:00
49c1a595ec
it was added (and wrong idx) on previous commit for some of my internal tests, this is not used anywhere so remove it.
796 lines
26 KiB
Go
796 lines
26 KiB
Go
/*
|
|
Context.go Implements: ./context/context.go ,
|
|
files: context_renderer.go, context_storage.go, context_request.go, context_response.go
|
|
*/
|
|
|
|
package iris
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"path"
|
|
"reflect"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/iris-contrib/errors"
|
|
"github.com/iris-contrib/formBinder"
|
|
"github.com/kataras/iris/config"
|
|
"github.com/kataras/iris/context"
|
|
"github.com/kataras/iris/sessions/store"
|
|
"github.com/kataras/iris/utils"
|
|
"github.com/klauspost/compress/gzip"
|
|
"github.com/valyala/fasthttp"
|
|
)
|
|
|
|
const (
|
|
// DefaultUserAgent default to 'iris' but it is not used anywhere yet
|
|
defaultUserAgent = "iris"
|
|
// ContentType represents the header["Content-Type"]
|
|
contentType = "Content-Type"
|
|
// ContentLength represents the header["Content-Length"]
|
|
contentLength = "Content-Length"
|
|
// ContentHTML is the string of text/html response headers
|
|
contentHTML = "text/html"
|
|
// ContentBINARY is the string of application/octet-stream response headers
|
|
contentBINARY = "application/octet-stream"
|
|
|
|
// LastModified "Last-Modified"
|
|
lastModified = "Last-Modified"
|
|
// IfModifiedSince "If-Modified-Since"
|
|
ifModifiedSince = "If-Modified-Since"
|
|
// ContentDisposition "Content-Disposition"
|
|
contentDisposition = "Content-Disposition"
|
|
|
|
// stopExecutionPosition used inside the Context, is the number which shows us that the context's middleware manualy stop the execution
|
|
stopExecutionPosition = 255
|
|
)
|
|
|
|
// this pool is used everywhere needed in the iris for example inside party-> Static
|
|
var gzipWriterPool = sync.Pool{New: func() interface{} { return &gzip.Writer{} }}
|
|
|
|
// errors
|
|
|
|
var (
|
|
errTemplateExecute = errors.New("Unable to execute a template. Trace: %s")
|
|
errFlashNotFound = errors.New("Unable to get flash message. Trace: Cookie does not exists")
|
|
errSessionNil = errors.New("Unable to set session, Config().Session.Provider is nil, please refer to the docs!")
|
|
errNoForm = errors.New("Request has no any valid form")
|
|
errWriteJSON = errors.New("Before JSON be written to the body, JSON Encoder returned an error. Trace: %s")
|
|
errRenderMarshalled = errors.New("Before +type Rendering, MarshalIndent returned an error. Trace: %s")
|
|
errReadBody = errors.New("While trying to read %s from the request body. Trace %s")
|
|
errServeContent = errors.New("While trying to serve content to the client. Trace %s")
|
|
)
|
|
|
|
type (
|
|
// Map is just a conversion for a map[string]interface{}
|
|
Map map[string]interface{}
|
|
// Context is resetting every time a request is coming to the server
|
|
// it is not good practice to use this object in goroutines, for these cases use the .Clone()
|
|
Context struct {
|
|
*fasthttp.RequestCtx
|
|
Params PathParameters
|
|
framework *Framework
|
|
//keep track all registed middleware (handlers)
|
|
middleware Middleware
|
|
sessionStore store.IStore
|
|
// pos is the position number of the Context, look .Next to understand
|
|
pos uint8
|
|
}
|
|
)
|
|
|
|
var _ context.IContext = &Context{}
|
|
|
|
// GetRequestCtx returns the current fasthttp context
|
|
func (ctx *Context) GetRequestCtx() *fasthttp.RequestCtx {
|
|
return ctx.RequestCtx
|
|
}
|
|
|
|
// Reset resets the Context with a given domain.Response and domain.Request
|
|
// the context is ready-to-use after that, just like a new Context
|
|
// I use it for zero rellocation memory
|
|
func (ctx *Context) Reset(reqCtx *fasthttp.RequestCtx) {
|
|
ctx.Params = ctx.Params[0:0]
|
|
ctx.sessionStore = nil
|
|
ctx.middleware = nil
|
|
ctx.RequestCtx = reqCtx
|
|
}
|
|
|
|
// Clone use that method if you want to use the context inside a goroutine
|
|
func (ctx *Context) Clone() context.IContext {
|
|
var cloneContext = *ctx
|
|
cloneContext.pos = 0
|
|
|
|
//copy params
|
|
p := ctx.Params
|
|
cpP := make(PathParameters, len(p))
|
|
copy(cpP, p)
|
|
cloneContext.Params = cpP
|
|
//copy middleware
|
|
m := ctx.middleware
|
|
cpM := make(Middleware, len(m))
|
|
copy(cpM, m)
|
|
cloneContext.middleware = cpM
|
|
|
|
// we don't copy the sessionStore for more than one reasons...
|
|
return &cloneContext
|
|
}
|
|
|
|
// Do calls the first handler only, it's like Next with negative pos, used only on Router&MemoryRouter
|
|
func (ctx *Context) Do() {
|
|
ctx.pos = 0
|
|
ctx.middleware[0].Serve(ctx)
|
|
}
|
|
|
|
// Next calls all the next handler from the middleware stack, it used inside a middleware
|
|
func (ctx *Context) Next() {
|
|
//set position to the next
|
|
ctx.pos++
|
|
midLen := uint8(len(ctx.middleware))
|
|
//run the next
|
|
if ctx.pos < midLen {
|
|
ctx.middleware[ctx.pos].Serve(ctx)
|
|
}
|
|
|
|
}
|
|
|
|
// StopExecution just sets the .pos to 255 in order to not move to the next middlewares(if any)
|
|
func (ctx *Context) StopExecution() {
|
|
ctx.pos = stopExecutionPosition
|
|
}
|
|
|
|
//
|
|
|
|
// IsStopped checks and returns true if the current position of the Context is 255, means that the StopExecution has called
|
|
func (ctx *Context) IsStopped() bool {
|
|
return ctx.pos == stopExecutionPosition
|
|
}
|
|
|
|
// GetHandlerName as requested returns the stack-name of the function which the Middleware is setted from
|
|
func (ctx *Context) GetHandlerName() string {
|
|
return runtime.FuncForPC(reflect.ValueOf(ctx.middleware[len(ctx.middleware)-1]).Pointer()).Name()
|
|
}
|
|
|
|
/* Request */
|
|
|
|
// Param returns the string representation of the key's path named parameter's value
|
|
func (ctx *Context) Param(key string) string {
|
|
return ctx.Params.Get(key)
|
|
}
|
|
|
|
// ParamInt returns the int representation of the key's path named parameter's value
|
|
func (ctx *Context) ParamInt(key string) (int, error) {
|
|
return strconv.Atoi(ctx.Param(key))
|
|
}
|
|
|
|
// ParamInt64 returns the int64 representation of the key's path named parameter's value
|
|
func (ctx *Context) ParamInt64(key string) (int64, error) {
|
|
return strconv.ParseInt(ctx.Param(key), 10, 64)
|
|
}
|
|
|
|
// URLParam returns the get parameter from a request , if any
|
|
func (ctx *Context) URLParam(key string) string {
|
|
return string(ctx.RequestCtx.Request.URI().QueryArgs().Peek(key))
|
|
}
|
|
|
|
// URLParams returns a map of a list of each url(query) parameter
|
|
func (ctx *Context) URLParams() map[string]string {
|
|
urlparams := make(map[string]string)
|
|
ctx.RequestCtx.Request.URI().QueryArgs().VisitAll(func(key, value []byte) {
|
|
urlparams[string(key)] = string(value)
|
|
})
|
|
return urlparams
|
|
}
|
|
|
|
// URLParamInt returns the url query parameter as int value from a request , returns error on parse fail
|
|
func (ctx *Context) URLParamInt(key string) (int, error) {
|
|
return strconv.Atoi(ctx.URLParam(key))
|
|
}
|
|
|
|
// URLParamInt64 returns the url query parameter as int64 value from a request , returns error on parse fail
|
|
func (ctx *Context) URLParamInt64(key string) (int64, error) {
|
|
return strconv.ParseInt(ctx.URLParam(key), 10, 64)
|
|
}
|
|
|
|
// MethodString returns the HTTP Method
|
|
func (ctx *Context) MethodString() string {
|
|
return utils.BytesToString(ctx.Method())
|
|
}
|
|
|
|
// HostString returns the Host of the request( the url as string )
|
|
func (ctx *Context) HostString() string {
|
|
return utils.BytesToString(ctx.Host())
|
|
}
|
|
|
|
// VirtualHostname returns the hostname that user registers, host path maybe differs from the real which is HostString, which taken from a net.listener
|
|
func (ctx *Context) VirtualHostname() string {
|
|
realhost := ctx.HostString()
|
|
hostname := realhost
|
|
virtualhost := ctx.framework.HTTPServer.VirtualHostname()
|
|
|
|
if portIdx := strings.IndexByte(hostname, ':'); portIdx > 0 {
|
|
hostname = hostname[0:portIdx]
|
|
}
|
|
if idxDotAnd := strings.LastIndexByte(hostname, '.'); idxDotAnd > 0 {
|
|
s := hostname[idxDotAnd:]
|
|
if s == ".1" {
|
|
hostname = strings.Replace(hostname, "127.0.0.1", virtualhost, 1)
|
|
}
|
|
} else {
|
|
hostname = strings.Replace(hostname, "localhost", virtualhost, 1)
|
|
}
|
|
|
|
return hostname
|
|
}
|
|
|
|
// PathString returns the full escaped path as string
|
|
// for unescaped use: ctx.RequestCtx.RequestURI() or RequestPath(escape bool)
|
|
func (ctx *Context) PathString() string {
|
|
return ctx.RequestPath(true)
|
|
}
|
|
|
|
// RequestPath returns the requested path
|
|
func (ctx *Context) RequestPath(escape bool) string {
|
|
if escape {
|
|
return utils.BytesToString(ctx.RequestCtx.Path())
|
|
}
|
|
return utils.BytesToString(ctx.RequestCtx.RequestURI())
|
|
}
|
|
|
|
// RequestIP gets just the Remote Address from the client.
|
|
func (ctx *Context) RequestIP() string {
|
|
if ip, _, err := net.SplitHostPort(strings.TrimSpace(ctx.RequestCtx.RemoteAddr().String())); err == nil {
|
|
return ip
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// RemoteAddr is like RequestIP but it checks for proxy servers also, tries to get the real client's request IP
|
|
func (ctx *Context) RemoteAddr() string {
|
|
header := string(ctx.RequestCtx.Request.Header.Peek("X-Real-Ip"))
|
|
realIP := strings.TrimSpace(header)
|
|
if realIP != "" {
|
|
return realIP
|
|
}
|
|
realIP = string(ctx.RequestCtx.Request.Header.Peek("X-Forwarded-For"))
|
|
idx := strings.IndexByte(realIP, ',')
|
|
if idx >= 0 {
|
|
realIP = realIP[0:idx]
|
|
}
|
|
realIP = strings.TrimSpace(realIP)
|
|
if realIP != "" {
|
|
return realIP
|
|
}
|
|
return ctx.RequestIP()
|
|
|
|
}
|
|
|
|
// RequestHeader returns the request header's value
|
|
// accepts one parameter, the key of the header (string)
|
|
// returns string
|
|
func (ctx *Context) RequestHeader(k string) string {
|
|
return utils.BytesToString(ctx.RequestCtx.Request.Header.Peek(k))
|
|
}
|
|
|
|
// PostFormValue returns a single value from post request's data
|
|
func (ctx *Context) PostFormValue(name string) string {
|
|
return string(ctx.FormValue(name))
|
|
}
|
|
|
|
// PostFormMulti returns a slice of string from post request's data
|
|
func (ctx *Context) PostFormMulti(name string) []string {
|
|
arrBytes := ctx.PostArgs().PeekMulti(name)
|
|
arrStr := make([]string, len(arrBytes))
|
|
for i, v := range arrBytes {
|
|
arrStr[i] = string(v)
|
|
}
|
|
return arrStr
|
|
}
|
|
|
|
// Subdomain returns the subdomain (string) of this request, if any
|
|
func (ctx *Context) Subdomain() (subdomain string) {
|
|
host := ctx.HostString()
|
|
if index := strings.IndexByte(host, '.'); index > 0 {
|
|
subdomain = host[0:index]
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// URLEncode returns the path encoded as url
|
|
// useful when you want to pass something to a database and be valid to retrieve it via context.Param
|
|
// use it only for special cases, when the default behavior doesn't suits you.
|
|
//
|
|
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
|
|
/* Credits to Manish Singh @kryptodev for URLEncode */
|
|
func URLEncode(path string) string {
|
|
if path == "" {
|
|
return ""
|
|
}
|
|
u := fasthttp.AcquireURI()
|
|
u.SetPath(path)
|
|
encodedPath := u.String()[8:]
|
|
fasthttp.ReleaseURI(u)
|
|
return encodedPath
|
|
}
|
|
|
|
// ReadJSON reads JSON from request's body
|
|
func (ctx *Context) ReadJSON(jsonObject interface{}) error {
|
|
data := ctx.RequestCtx.Request.Body()
|
|
|
|
decoder := json.NewDecoder(strings.NewReader(string(data)))
|
|
err := decoder.Decode(jsonObject)
|
|
|
|
//err != nil fix by @shiena
|
|
if err != nil && err != io.EOF {
|
|
return errReadBody.Format("JSON", err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReadXML reads XML from request's body
|
|
func (ctx *Context) ReadXML(xmlObject interface{}) error {
|
|
data := ctx.RequestCtx.Request.Body()
|
|
|
|
decoder := xml.NewDecoder(strings.NewReader(string(data)))
|
|
err := decoder.Decode(xmlObject)
|
|
//err != nil fix by @shiena
|
|
if err != nil && err != io.EOF {
|
|
return errReadBody.Format("XML", err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReadForm binds the formObject with the form data
|
|
// it supports any kind of struct
|
|
func (ctx *Context) ReadForm(formObject interface{}) error {
|
|
reqCtx := ctx.RequestCtx
|
|
// first check if we have multipart form
|
|
multipartForm, err := reqCtx.MultipartForm()
|
|
if err == nil {
|
|
//we have multipart form
|
|
return errReadBody.With(formBinder.Decode(multipartForm.Value, formObject))
|
|
}
|
|
// if no multipart and post arguments ( means normal form)
|
|
|
|
if reqCtx.PostArgs().Len() == 0 && reqCtx.QueryArgs().Len() == 0 {
|
|
return errReadBody.With(errNoForm.Return())
|
|
}
|
|
|
|
form := make(map[string][]string, reqCtx.PostArgs().Len()+reqCtx.QueryArgs().Len())
|
|
|
|
reqCtx.PostArgs().VisitAll(func(k []byte, v []byte) {
|
|
key := string(k)
|
|
value := string(v)
|
|
// for slices
|
|
if form[key] != nil {
|
|
form[key] = append(form[key], value)
|
|
} else {
|
|
form[key] = []string{value}
|
|
}
|
|
|
|
})
|
|
|
|
reqCtx.QueryArgs().VisitAll(func(k []byte, v []byte) {
|
|
key := string(k)
|
|
value := string(v)
|
|
// for slices
|
|
if form[key] != nil {
|
|
form[key] = append(form[key], value)
|
|
} else {
|
|
form[key] = []string{value}
|
|
}
|
|
})
|
|
|
|
return errReadBody.With(formBinder.Decode(form, formObject))
|
|
}
|
|
|
|
/* Response */
|
|
|
|
// SetContentType sets the response writer's header key 'Content-Type' to a given value(s)
|
|
func (ctx *Context) SetContentType(s string) {
|
|
ctx.RequestCtx.Response.Header.Set(contentType, s)
|
|
}
|
|
|
|
// SetHeader write to the response writer's header to a given key the given value(s)
|
|
//
|
|
// Note: If you want to send a multi-line string as header's value use: strings.TrimSpace first.
|
|
func (ctx *Context) SetHeader(k string, v string) {
|
|
//v = strings.TrimSpace(v)
|
|
ctx.RequestCtx.Response.Header.Set(k, v)
|
|
}
|
|
|
|
// Redirect redirect sends a redirect response the client
|
|
// accepts 2 parameters string and an optional int
|
|
// first parameter is the url to redirect
|
|
// second parameter is the http status should send, default is 302 (StatusFound), you can set it to 301 (Permant redirect), if that's nessecery
|
|
func (ctx *Context) Redirect(urlToRedirect string, statusHeader ...int) {
|
|
httpStatus := StatusFound // temporary redirect
|
|
if statusHeader != nil && len(statusHeader) > 0 && statusHeader[0] > 0 {
|
|
httpStatus = statusHeader[0]
|
|
}
|
|
ctx.RequestCtx.Redirect(urlToRedirect, httpStatus)
|
|
ctx.StopExecution()
|
|
}
|
|
|
|
// RedirectTo does the same thing as Redirect but instead of receiving a uri or path it receives a route name
|
|
func (ctx *Context) RedirectTo(routeName string, args ...interface{}) {
|
|
s := ctx.framework.URL(routeName, args...)
|
|
if s != "" {
|
|
ctx.Redirect(s, StatusFound)
|
|
}
|
|
}
|
|
|
|
// NotFound emits an error 404 to the client, using the custom http errors
|
|
// if no custom errors provided then it sends the default error message
|
|
func (ctx *Context) NotFound() {
|
|
ctx.framework.EmitError(StatusNotFound, ctx)
|
|
}
|
|
|
|
// Panic emits an error 500 to the client, using the custom http errors
|
|
// if no custom errors rpovided then it sends the default error message
|
|
func (ctx *Context) Panic() {
|
|
ctx.framework.EmitError(StatusInternalServerError, ctx)
|
|
}
|
|
|
|
// EmitError executes the custom error by the http status code passed to the function
|
|
func (ctx *Context) EmitError(statusCode int) {
|
|
ctx.framework.EmitError(statusCode, ctx)
|
|
ctx.StopExecution()
|
|
}
|
|
|
|
// Write writes a string to the client, something like fmt.Printf but for the web
|
|
func (ctx *Context) Write(format string, a ...interface{}) {
|
|
//this doesn't work with gzip, so just write the []byte better |ctx.ResponseWriter.WriteString(fmt.Sprintf(format, a...))
|
|
ctx.RequestCtx.WriteString(fmt.Sprintf(format, a...))
|
|
}
|
|
|
|
// HTML writes html string with a http status
|
|
func (ctx *Context) HTML(httpStatus int, htmlContents string) {
|
|
ctx.SetContentType(contentHTML + ctx.framework.rest.CompiledCharset)
|
|
ctx.RequestCtx.SetStatusCode(httpStatus)
|
|
ctx.RequestCtx.WriteString(htmlContents)
|
|
}
|
|
|
|
// Data writes out the raw bytes as binary data.
|
|
func (ctx *Context) Data(status int, v []byte) error {
|
|
return ctx.framework.rest.Data(ctx.RequestCtx, status, v)
|
|
}
|
|
|
|
// RenderWithStatus builds up the response from the specified template and bindings.
|
|
// Note: parameter layout has meaning only when using the iris.HTMLTemplate
|
|
func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, layout ...string) error {
|
|
ctx.SetStatusCode(status)
|
|
return ctx.framework.templates.Render(ctx, name, binding, layout...)
|
|
}
|
|
|
|
// Render same as .RenderWithStatus but with status to iris.StatusOK (200)
|
|
func (ctx *Context) Render(name string, binding interface{}, layout ...string) error {
|
|
return ctx.RenderWithStatus(StatusOK, name, binding, layout...)
|
|
}
|
|
|
|
// MustRender same as .Render but returns 500 internal server http status (error) if rendering fail
|
|
func (ctx *Context) MustRender(name string, binding interface{}, layout ...string) {
|
|
if err := ctx.Render(name, binding, layout...); err != nil {
|
|
ctx.Panic()
|
|
ctx.framework.Logger.Dangerf("MustRender panics for client with IP: %s On template: %s.Trace: %s\n", ctx.RemoteAddr(), name, err)
|
|
}
|
|
}
|
|
|
|
// TemplateString accepts a template filename, its context data and returns the result of the parsed template (string)
|
|
// if any error returns empty string
|
|
func (ctx *Context) TemplateString(name string, binding interface{}, layout ...string) string {
|
|
return ctx.framework.TemplateString(name, binding, layout...)
|
|
}
|
|
|
|
// JSON marshals the given interface object and writes the JSON response.
|
|
func (ctx *Context) JSON(status int, v interface{}) error {
|
|
return ctx.framework.rest.JSON(ctx.RequestCtx, status, v)
|
|
}
|
|
|
|
// JSONP marshals the given interface object and writes the JSON response.
|
|
func (ctx *Context) JSONP(status int, callback string, v interface{}) error {
|
|
return ctx.framework.rest.JSONP(ctx.RequestCtx, status, callback, v)
|
|
}
|
|
|
|
// Text writes out a string as plain text.
|
|
func (ctx *Context) Text(status int, v string) error {
|
|
return ctx.framework.rest.Text(ctx.RequestCtx, status, v)
|
|
}
|
|
|
|
// XML marshals the given interface object and writes the XML response.
|
|
func (ctx *Context) XML(status int, v interface{}) error {
|
|
return ctx.framework.rest.XML(ctx.RequestCtx, status, v)
|
|
}
|
|
|
|
// MarkdownString parses the (dynamic) markdown string and returns the converted html string
|
|
func (ctx *Context) MarkdownString(markdownText string) string {
|
|
return ctx.framework.rest.Markdown([]byte(markdownText))
|
|
}
|
|
|
|
// Markdown parses and renders to the client a particular (dynamic) markdown string
|
|
// accepts two parameters
|
|
// first is the http status code
|
|
// second is the markdown string
|
|
func (ctx *Context) Markdown(status int, markdown string) {
|
|
ctx.HTML(status, ctx.MarkdownString(markdown))
|
|
}
|
|
|
|
// ExecuteTemplate executes a simple html template, you can use that if you already have the cached templates
|
|
// the recommended way to render is to use iris.Templates("./templates/path/*.html") and ctx.RenderFile("filename.html",struct{})
|
|
// accepts 2 parameters
|
|
// the first parameter is the template (*template.Template)
|
|
// the second parameter is the page context (interfac{})
|
|
// returns an error if any errors occurs while executing this template
|
|
func (ctx *Context) ExecuteTemplate(tmpl *template.Template, pageContext interface{}) error {
|
|
ctx.RequestCtx.SetContentType(contentHTML + ctx.framework.rest.CompiledCharset)
|
|
return errTemplateExecute.With(tmpl.Execute(ctx.RequestCtx.Response.BodyWriter(), pageContext))
|
|
}
|
|
|
|
// ServeContent serves content, headers are autoset
|
|
// receives three parameters, it's low-level function, instead you can use .ServeFile(string)
|
|
//
|
|
// You can define your own "Content-Type" header also, after this function call
|
|
func (ctx *Context) ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error {
|
|
if t, err := time.Parse(config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && modtime.Before(t.Add(1*time.Second)) {
|
|
ctx.RequestCtx.Response.Header.Del(contentType)
|
|
ctx.RequestCtx.Response.Header.Del(contentLength)
|
|
ctx.RequestCtx.SetStatusCode(StatusNotModified)
|
|
return nil
|
|
}
|
|
|
|
ctx.RequestCtx.Response.Header.Set(contentType, utils.TypeByExtension(filename))
|
|
ctx.RequestCtx.Response.Header.Set(lastModified, modtime.UTC().Format(config.TimeFormat))
|
|
ctx.RequestCtx.SetStatusCode(StatusOK)
|
|
var out io.Writer
|
|
if gzipCompression {
|
|
ctx.RequestCtx.Response.Header.Add("Content-Encoding", "gzip")
|
|
gzipWriter := gzipWriterPool.Get().(*gzip.Writer)
|
|
gzipWriter.Reset(ctx.RequestCtx.Response.BodyWriter())
|
|
defer gzipWriter.Close()
|
|
defer gzipWriterPool.Put(gzipWriter)
|
|
out = gzipWriter
|
|
} else {
|
|
out = ctx.RequestCtx.Response.BodyWriter()
|
|
|
|
}
|
|
_, err := io.Copy(out, content)
|
|
return errServeContent.With(err)
|
|
}
|
|
|
|
// ServeFile serves a view file, to send a file ( zip for example) to the client you should use the SendFile(serverfilename,clientfilename)
|
|
// receives two parameters
|
|
// filename/path (string)
|
|
// gzipCompression (bool)
|
|
//
|
|
// You can define your own "Content-Type" header also, after this function call
|
|
func (ctx *Context) ServeFile(filename string, gzipCompression bool) error {
|
|
f, err := os.Open(filename)
|
|
if err != nil {
|
|
return fmt.Errorf("%d", 404)
|
|
}
|
|
defer f.Close()
|
|
fi, _ := f.Stat()
|
|
if fi.IsDir() {
|
|
filename = path.Join(filename, "index.html")
|
|
f, err = os.Open(filename)
|
|
if err != nil {
|
|
return fmt.Errorf("%d", 404)
|
|
}
|
|
fi, _ = f.Stat()
|
|
}
|
|
return ctx.ServeContent(f, fi.Name(), fi.ModTime(), gzipCompression)
|
|
}
|
|
|
|
// SendFile sends file for force-download to the client
|
|
//
|
|
// You can define your own "Content-Type" header also, after this function call
|
|
// for example: ctx.Response.Header.Set("Content-Type","thecontent/type")
|
|
func (ctx *Context) SendFile(filename string, destinationName string) error {
|
|
err := ctx.ServeFile(filename, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx.RequestCtx.Response.Header.Set(contentDisposition, "attachment;filename="+destinationName)
|
|
return nil
|
|
}
|
|
|
|
// Stream same as StreamWriter
|
|
func (ctx *Context) Stream(cb func(writer *bufio.Writer)) {
|
|
ctx.StreamWriter(cb)
|
|
}
|
|
|
|
// StreamWriter registers the given stream writer for populating
|
|
// response body.
|
|
//
|
|
//
|
|
// This function may be used in the following cases:
|
|
//
|
|
// * if response body is too big (more than 10MB).
|
|
// * if response body is streamed from slow external sources.
|
|
// * if response body must be streamed to the client in chunks.
|
|
// (aka `http server push`).
|
|
func (ctx *Context) StreamWriter(cb func(writer *bufio.Writer)) {
|
|
ctx.RequestCtx.SetBodyStreamWriter(cb)
|
|
}
|
|
|
|
// StreamReader sets response body stream and, optionally body size.
|
|
//
|
|
// If bodySize is >= 0, then the bodyStream must provide exactly bodySize bytes
|
|
// before returning io.EOF.
|
|
//
|
|
// If bodySize < 0, then bodyStream is read until io.EOF.
|
|
//
|
|
// bodyStream.Close() is called after finishing reading all body data
|
|
// if it implements io.Closer.
|
|
//
|
|
// See also StreamReader.
|
|
func (ctx *Context) StreamReader(bodyStream io.Reader, bodySize int) {
|
|
ctx.RequestCtx.Response.SetBodyStream(bodyStream, bodySize)
|
|
}
|
|
|
|
/* Storage */
|
|
|
|
// Get returns the user's value from a key
|
|
// if doesn't exists returns nil
|
|
func (ctx *Context) Get(key string) interface{} {
|
|
return ctx.RequestCtx.UserValue(key)
|
|
}
|
|
|
|
// GetFmt returns a value which has this format: func(format string, args ...interface{}) string
|
|
// if doesn't exists returns nil
|
|
func (ctx *Context) GetFmt(key string) func(format string, args ...interface{}) string {
|
|
if v, ok := ctx.Get(key).(func(format string, args ...interface{}) string); ok {
|
|
return v
|
|
}
|
|
return func(format string, args ...interface{}) string { return "" }
|
|
|
|
}
|
|
|
|
// GetString same as Get but returns the value as string
|
|
// if nothing founds returns empty string ""
|
|
func (ctx *Context) GetString(key string) string {
|
|
if v, ok := ctx.Get(key).(string); ok {
|
|
return v
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// GetInt same as Get but returns the value as int
|
|
// if nothing founds returns -1
|
|
func (ctx *Context) GetInt(key string) int {
|
|
if v, ok := ctx.Get(key).(int); ok {
|
|
return v
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
// Set sets a value to a key in the values map
|
|
func (ctx *Context) Set(key string, value interface{}) {
|
|
ctx.RequestCtx.SetUserValue(key, value)
|
|
}
|
|
|
|
// GetCookie returns cookie's value by it's name
|
|
// returns empty string if nothing was found
|
|
func (ctx *Context) GetCookie(name string) (val string) {
|
|
bcookie := ctx.RequestCtx.Request.Header.Cookie(name)
|
|
if bcookie != nil {
|
|
val = string(bcookie)
|
|
}
|
|
return
|
|
}
|
|
|
|
// SetCookie adds a cookie
|
|
func (ctx *Context) SetCookie(cookie *fasthttp.Cookie) {
|
|
ctx.RequestCtx.Response.Header.SetCookie(cookie)
|
|
}
|
|
|
|
// SetCookieKV adds a cookie, receives just a key(string) and a value(string)
|
|
func (ctx *Context) SetCookieKV(key, value string) {
|
|
c := fasthttp.AcquireCookie() // &fasthttp.Cookie{}
|
|
c.SetKey(key)
|
|
c.SetValue(value)
|
|
c.SetHTTPOnly(true)
|
|
c.SetExpire(time.Now().Add(time.Duration(120) * time.Minute))
|
|
ctx.SetCookie(c)
|
|
fasthttp.ReleaseCookie(c)
|
|
}
|
|
|
|
// RemoveCookie deletes a cookie by it's name/key
|
|
func (ctx *Context) RemoveCookie(name string) {
|
|
cookie := fasthttp.AcquireCookie()
|
|
cookie.SetKey(name)
|
|
cookie.SetValue("")
|
|
cookie.SetPath("/")
|
|
cookie.SetHTTPOnly(true)
|
|
exp := time.Now().Add(-time.Duration(1) * time.Minute) //RFC says 1 second, but make sure 1 minute because we are using fasthttp
|
|
cookie.SetExpire(exp)
|
|
ctx.Response.Header.SetCookie(cookie)
|
|
fasthttp.ReleaseCookie(cookie)
|
|
}
|
|
|
|
// GetFlash get a flash message by it's key
|
|
// after this action the messages is removed
|
|
// returns string, if the cookie doesn't exists the string is empty
|
|
func (ctx *Context) GetFlash(key string) string {
|
|
val, err := ctx.GetFlashBytes(key)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return string(val)
|
|
}
|
|
|
|
// GetFlashBytes get a flash message by it's key
|
|
// after this action the messages is removed
|
|
// returns []byte along with an error if the cookie doesn't exists or decode fails
|
|
func (ctx *Context) GetFlashBytes(key string) (value []byte, err error) {
|
|
cookieValue := string(ctx.RequestCtx.Request.Header.Cookie(key))
|
|
if cookieValue == "" {
|
|
err = errFlashNotFound.Return()
|
|
} else {
|
|
value, err = base64.URLEncoding.DecodeString(cookieValue)
|
|
//remove the message
|
|
ctx.RemoveCookie(key)
|
|
//it should'b be removed until the next reload, so we don't do that: ctx.Request.Header.SetCookie(key, "")
|
|
}
|
|
return
|
|
}
|
|
|
|
// SetFlash sets a flash message, accepts 2 parameters the key(string) and the value(string)
|
|
func (ctx *Context) SetFlash(key string, value string) {
|
|
ctx.SetFlashBytes(key, utils.StringToBytes(value))
|
|
}
|
|
|
|
// SetFlashBytes sets a flash message, accepts 2 parameters the key(string) and the value([]byte)
|
|
func (ctx *Context) SetFlashBytes(key string, value []byte) {
|
|
c := fasthttp.AcquireCookie()
|
|
c.SetKey(key)
|
|
c.SetValue(base64.URLEncoding.EncodeToString(value))
|
|
c.SetPath("/")
|
|
c.SetHTTPOnly(true)
|
|
ctx.RequestCtx.Response.Header.SetCookie(c)
|
|
fasthttp.ReleaseCookie(c)
|
|
}
|
|
|
|
// Session returns the current session store, returns nil if provider is ""
|
|
func (ctx *Context) Session() store.IStore {
|
|
if ctx.framework.sessions == nil || ctx.framework.Config.Sessions.Provider == "" { //the second check can be changed on runtime, users are able to turn off the sessions by setting provider to ""
|
|
return nil
|
|
}
|
|
|
|
if ctx.sessionStore == nil {
|
|
ctx.sessionStore = ctx.framework.sessions.Start(ctx)
|
|
}
|
|
return ctx.sessionStore
|
|
}
|
|
|
|
// SessionDestroy destroys the whole session, calls the provider's destroy and remove the cookie
|
|
func (ctx *Context) SessionDestroy() {
|
|
if ctx.framework.sessions != nil {
|
|
if store := ctx.Session(); store != nil {
|
|
ctx.framework.sessions.Destroy(ctx)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Log logs to the iris defined logger
|
|
func (ctx *Context) Log(format string, a ...interface{}) {
|
|
ctx.framework.Logger.Printf(format, a...)
|
|
}
|