/* Context.go Implements: ./context/context.go */ package iris import ( "bufio" "encoding/base64" "encoding/json" "encoding/xml" "fmt" "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/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" // contentEncodingHeader represents the header["Content-Encoding"] contentEncodingHeader = "Content-Encoding" // varyHeader represents the header "Vary" varyHeader = "Vary" // acceptEncodingHeader represents the header key & value "Accept-Encoding" acceptEncodingHeader = "Accept-Encoding" // ContentHTML is the string of text/html response headers contentHTML = "text/html" // ContentBinary header value for binary data. contentBinary = "application/octet-stream" // ContentJSON header value for JSON data. contentJSON = "application/json" // ContentJSONP header value for JSONP data. contentJSONP = "application/javascript" // ContentText header value for Text data. contentText = "text/plain" // ContentXML header value for XML data. contentXML = "text/xml" // contentMarkdown custom key/content type, the real is the text/html contentMarkdown = "text/markdown" // 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 // used inside GetFlash to store the lifetime request flash messages flashMessagesStoreContextKey = "_iris_flash_messages_" flashMessageCookiePrefix = "_iris_flash_message_" cookieHeaderID = "Cookie: " cookieHeaderIDLen = len(cookieHeaderID) ) // 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{} // should not be used inside Render when PongoEngine is used. 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 session *session // 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 } // 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.Servers.Main().Hostname() if portIdx := strings.IndexByte(hostname, ':'); portIdx > 0 { hostname = hostname[0:portIdx] } if idxDotAnd := strings.LastIndexByte(hostname, '.'); idxDotAnd > 0 { s := hostname[idxDotAnd:] // means that we have the request's host mymachine.com or 127.0.0.1/0.0.0.0, but for the second option we will need to replace it with the hostname that the dev was registered // this needed to parse correct the {{ url }} iris global template engine's function if s == ".1" { hostname = strings.Replace(hostname, "127.0.0.1", virtualhost, 1) } else if s == ".0" { hostname = strings.Replace(hostname, "0.0.0.0", 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)) } // FormValueString returns a single value, as string, from post request's data func (ctx *Context) FormValueString(name string) string { return string(ctx.FormValue(name)) } // FormValues returns a slice of string from post request's data func (ctx *Context) FormValues(name string) []string { arrBytes := ctx.PostArgs().PeekMulti(name) arrStr := make([]string, len(arrBytes)) for i, v := range arrBytes { arrStr[i] = string(v) } return arrStr } // PostValuesAll returns all post data values with their keys // multipart, form data, get & post query arguments func (ctx *Context) PostValuesAll() (valuesAll map[string][]string) { reqCtx := ctx.RequestCtx valuesAll = make(map[string][]string) // first check if we have multipart form multipartForm, err := reqCtx.MultipartForm() if err == nil { //we have multipart form return multipartForm.Value } // if no multipart and post arguments ( means normal form) if reqCtx.PostArgs().Len() == 0 && reqCtx.QueryArgs().Len() == 0 { return // no found } reqCtx.PostArgs().VisitAll(func(k []byte, v []byte) { key := string(k) value := string(v) // for slices if valuesAll[key] != nil { valuesAll[key] = append(valuesAll[key], value) } else { valuesAll[key] = []string{value} } }) reqCtx.QueryArgs().VisitAll(func(k []byte, v []byte) { key := string(k) value := string(v) // for slices if valuesAll[key] != nil { valuesAll[key] = append(valuesAll[key], value) } else { valuesAll[key] = []string{value} } }) return } // PostValues returns the post data values as []string of a single key/name func (ctx *Context) PostValues(name string) []string { values := make([]string, 0) if v := ctx.PostValuesAll(); v != nil && len(v) > 0 { values = v[name] } return values } // PostValue returns the post data value of a single key/name // returns an empty string if nothing found func (ctx *Context) PostValue(name string) string { if v := ctx.PostValues(name); len(v) > 0 { return v[0] } return "" } // 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 } // 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 // a 'temporary-redirect-like' wich works better than for our purpose 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...)) } func (ctx *Context) clientAllowsGzip() bool { if h := ctx.RequestHeader(acceptEncodingHeader); h != "" { for _, v := range strings.Split(h, ";") { if strings.Contains(v, "gzip") { // we do Contains because sometimes browsers has the q=, we don't use it atm. || strings.Contains(v,"deflate"){ return true } } } return false } // Gzip accepts bytes, which are compressed to gzip format and sent to the client func (ctx *Context) Gzip(b []byte, status int) { ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader) if ctx.clientAllowsGzip() { _, err := fasthttp.WriteGzip(ctx.RequestCtx.Response.BodyWriter(), b) if err == nil { ctx.SetHeader(contentEncodingHeader, "gzip") } } } // RenderWithStatus builds up the response from the specified template or a response engine. // Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or response engine func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, options ...map[string]interface{}) error { ctx.SetStatusCode(status) if strings.IndexByte(name, '.') > -1 { //we have template return ctx.framework.templates.getBy(name).execute(ctx, name, binding, options...) } return ctx.framework.responses.getBy(name).render(ctx, binding, options...) } // Render same as .RenderWithStatus but with status to iris.StatusOK (200) if no previous status exists // builds up the response from the specified template or a response engine. // Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or response engine func (ctx *Context) Render(name string, binding interface{}, options ...map[string]interface{}) error { errCode := ctx.RequestCtx.Response.StatusCode() if errCode <= 0 { errCode = StatusOK } return ctx.RenderWithStatus(errCode, name, binding, options...) } // MustRender same as .Render but returns 500 internal server http status (error) if rendering fail // builds up the response from the specified template or a response engine. // Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or response engine func (ctx *Context) MustRender(name string, binding interface{}, options ...map[string]interface{}) { if err := ctx.Render(name, binding, options...); 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{}, options ...map[string]interface{}) string { return ctx.framework.TemplateString(name, binding, options...) } // HTML writes html string with a http status func (ctx *Context) HTML(status int, htmlContents string) { if err := ctx.RenderWithStatus(status, contentHTML, htmlContents); err != nil { // if no response engine found for text/html ctx.SetContentType(contentHTML + "; charset=" + ctx.framework.Config.Charset) ctx.RequestCtx.SetStatusCode(status) ctx.RequestCtx.WriteString(htmlContents) } } // Data writes out the raw bytes as binary data. func (ctx *Context) Data(status int, v []byte) error { return ctx.RenderWithStatus(status, contentBinary, v) } // JSON marshals the given interface object and writes the JSON response. func (ctx *Context) JSON(status int, v interface{}) error { return ctx.RenderWithStatus(status, contentJSON, 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.RenderWithStatus(status, contentJSONP, v, map[string]interface{}{"callback": callback}) } // Text writes out a string as plain text. func (ctx *Context) Text(status int, v string) error { return ctx.RenderWithStatus(status, contentText, v) } // XML marshals the given interface object and writes the XML response. func (ctx *Context) XML(status int, v interface{}) error { return ctx.RenderWithStatus(status, contentXML, v) } // MarkdownString parses the (dynamic) markdown string and returns the converted html string func (ctx *Context) MarkdownString(markdownText string) string { return ctx.framework.ResponseString(contentMarkdown, 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)) } // 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.clientAllowsGzip() { ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader) ctx.SetHeader(contentEncodingHeader, "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 // This function doesn't implement resuming, use ctx.RequestCtx.SendFile/fasthttp.ServeFileUncompressed(ctx.RequestCtx,path)/ServeFile(ctx.RequestCtx,path) instead 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") // This function doesn't implement resuming, use ctx.RequestCtx.SendFile instead 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) } // VisitAllCookies takes a visitor which loops on each (request's) cookie key and value // // Note: the method ctx.Request.Header.VisitAllCookie by fasthttp, has a strange bug which I cannot solve at the moment. // This is the reason which this function exists and should be used instead of fasthttp's built'n. func (ctx *Context) VisitAllCookies(visitor func(key string, value string)) { // strange bug, this doesnt works also: cookieHeaderContent := ctx.Request.Header.Peek("Cookie")/User-Agent tested also headerbody := string(ctx.Request.Header.Header()) headerlines := strings.Split(headerbody, "\n") for _, s := range headerlines { if len(s) > cookieHeaderIDLen { if s[0:cookieHeaderIDLen] == cookieHeaderID { contents := s[cookieHeaderIDLen:] values := strings.Split(contents, "; ") for _, s := range values { keyvalue := strings.SplitN(s, "=", 2) visitor(keyvalue[0], keyvalue[1]) } } } } } // 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) { ctx.Response.Header.DelCookie(name) 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 let's do it 1 minute to make sure is working... cookie.SetExpire(exp) ctx.Response.Header.SetCookie(cookie) fasthttp.ReleaseCookie(cookie) // delete request's cookie also, which is temporarly available ctx.Request.Header.DelCookie(name) } // GetFlashes returns all the flash messages for available for this request func (ctx *Context) GetFlashes() map[string]string { // if already taken at least one time, this will be filled if messages := ctx.Get(flashMessagesStoreContextKey); messages != nil { if m, isMap := messages.(map[string]string); isMap { return m } } else { flashMessageFound := false // else first time, get all flash cookie keys(the prefix will tell us which is a flash message), and after get all one-by-one using the GetFlash. flashMessageCookiePrefixLen := len(flashMessageCookiePrefix) ctx.VisitAllCookies(func(key string, value string) { if len(key) > flashMessageCookiePrefixLen { if key[0:flashMessageCookiePrefixLen] == flashMessageCookiePrefix { unprefixedKey := key[flashMessageCookiePrefixLen:] _, err := ctx.GetFlash(unprefixedKey) // this func will add to the list (flashMessagesStoreContextKey) also if err == nil { flashMessageFound = true } } } }) // if we found at least one flash message then re-execute this function to return the list if flashMessageFound { return ctx.GetFlashes() } } return nil } func (ctx *Context) decodeFlashCookie(key string) (string, string) { cookieKey := flashMessageCookiePrefix + key cookieValue := string(ctx.RequestCtx.Request.Header.Cookie(cookieKey)) if cookieValue != "" { v, e := base64.URLEncoding.DecodeString(cookieValue) if e == nil { return cookieKey, string(v) } } return "", "" } // GetFlash get a flash message by it's key // returns the value as string and an error // // if the cookie doesn't exists the string is empty and the error is filled // after the request's life the value is removed func (ctx *Context) GetFlash(key string) (string, error) { // first check if flash exists from this request's lifetime, if yes return that else continue to get the cookie storeExists := false if messages := ctx.Get(flashMessagesStoreContextKey); messages != nil { m, isMap := messages.(map[string]string) if !isMap { return "", fmt.Errorf("Flash store is not a map[string]string. This suppose will never happen, please report this bug.") } storeExists = true // in order to skip the check later for k, v := range m { if k == key && v != "" { return v, nil } } } cookieKey, cookieValue := ctx.decodeFlashCookie(key) if cookieValue == "" { return "", errFlashNotFound.Return() } // store this flash message to the lifetime request's local storage, // I choose this method because no need to store it if not used at all if storeExists { ctx.Get(flashMessagesStoreContextKey).(map[string]string)[key] = cookieValue } else { flashStoreMap := make(map[string]string) flashStoreMap[key] = cookieValue ctx.Set(flashMessagesStoreContextKey, flashStoreMap) } //remove the real cookie, no need to have that, we stored it on lifetime request ctx.RemoveCookie(cookieKey) return cookieValue, nil //it should'b be removed until the next reload, so we don't do that: ctx.Request.Header.SetCookie(key, "") } // SetFlash sets a flash message, accepts 2 parameters the key(string) and the value(string) // the value will be available on the NEXT request func (ctx *Context) SetFlash(key string, value string) { c := fasthttp.AcquireCookie() c.SetKey(flashMessageCookiePrefix + key) c.SetValue(base64.URLEncoding.EncodeToString([]byte(value))) c.SetPath("/") c.SetHTTPOnly(true) ctx.RequestCtx.Response.Header.SetCookie(c) fasthttp.ReleaseCookie(c) } // Session returns the current session func (ctx *Context) Session() context.Session { if ctx.framework.sessions == nil { // this should never return nil but FOR ANY CASE, on future changes. return nil } if ctx.session == nil { ctx.session = ctx.framework.sessions.start(ctx) } return ctx.session } // SessionDestroy destroys the whole session, calls the provider's destroy and remove the cookie func (ctx *Context) SessionDestroy() { if sess := ctx.Session(); sess != 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...) }