package iris import ( "bufio" "bytes" "encoding/base64" "encoding/json" "encoding/xml" "fmt" "io" "net" "os" "path" "reflect" "runtime" "strconv" "strings" "time" "github.com/iris-contrib/formBinder" "github.com/kataras/go-errors" "github.com/kataras/go-fs" "github.com/kataras/go-sessions" "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 & Javascript data. contentJSONP = "application/javascript" // ContentJavascript header value for Javascript/JSONP // conversional contentJavascript = "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) ) // 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 framework *Framework //keep track all registed middleware (handlers) Middleware Middleware // exported because is useful for debugging session sessions.Session // Pos is the position number of the Context, look .Next to understand Pos uint8 // exported because is useful for debugging } ) // 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 */ // 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 string(ctx.Method()) } // HostString returns the Host of the request( the url as string ) func (ctx *Context) HostString() string { return string(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.mux.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(!ctx.framework.Config.DisablePathEscape) } // RequestPath returns the requested path func (ctx *Context) RequestPath(escape bool) string { if escape { // return utils.BytesToString(ctx.RequestCtx.Path()) return string(ctx.RequestCtx.URI().PathOriginal()) } return string(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 string(ctx.RequestCtx.Request.Header.Peek(k)) } // IsAjax returns true if this request is an 'ajax request'( XMLHttpRequest) // // Read more at: http://www.w3schools.com/ajax/ func (ctx *Context) IsAjax() bool { return ctx.RequestHeader("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest" } // 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 { var values []string 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 } // BodyDecoder is an interface which any struct can implement in order to customize the decode action // from ReadJSON and ReadXML // // Trivial example of this could be: // type User struct { Username string } // // func (u *User) Decode(data []byte) error { // return json.Unmarshal(data, u) // } // // the 'context.ReadJSON/ReadXML(&User{})' will call the User's // Decode option to decode the request body // // Note: This is totally optionally, the default decoders // for ReadJSON is the encoding/json and for ReadXML is the encoding/xml type BodyDecoder interface { Decode(data []byte) error } // ReadJSON reads JSON from request's body func (ctx *Context) ReadJSON(jsonObject interface{}) error { rawData := ctx.Request.Body() // check if the jsonObject contains its own decode // in this case the jsonObject should be a pointer also, // but this is up to the user's custom Decode implementation* // // See 'BodyDecoder' for more if decoder, isDecoder := jsonObject.(BodyDecoder); isDecoder { return decoder.Decode(rawData) } // check if jsonObject is already a pointer, if yes then pass as it's if reflect.TypeOf(jsonObject).Kind() == reflect.Ptr { return json.Unmarshal(rawData, jsonObject) } // finally, if the jsonObject doesn't contains a self-body decoder and it's not a pointer return json.Unmarshal(rawData, &jsonObject) } // ReadXML reads XML from request's body func (ctx *Context) ReadXML(xmlObject interface{}) error { rawData := ctx.Request.Body() // check if the xmlObject contains its own decode // in this case the jsonObject should be a pointer also, // but this is up to the user's custom Decode implementation* // // See 'BodyDecoder' for more if decoder, isDecoder := xmlObject.(BodyDecoder); isDecoder { return decoder.Decode(rawData) } // check if xmlObject is already a pointer, if yes then pass as it's if reflect.TypeOf(xmlObject).Kind() == reflect.Ptr { return xml.Unmarshal(rawData, xmlObject) } // finally, if the xmlObject doesn't contains a self-body decoder and it's not a pointer return xml.Unmarshal(rawData, &xmlObject) } // 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) } 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' which works better than for our purpose if statusHeader != nil && len(statusHeader) > 0 && statusHeader[0] > 0 { httpStatus = statusHeader[0] } ctx.RequestCtx.Redirect(urlToRedirect, httpStatus) /* you can use one of these if you want to customize the redirection: 1. u := fasthttp.AcquireURI() ctx.URI().CopyTo(u) u.Update(urlToRedirect) ctx.SetHeader("Location", string(u.FullURI())) fasthttp.ReleaseURI(u) ctx.SetStatusCode(httpStatus) 2. ctx.SetHeader("Location", urlToRedirect) ctx.SetStatusCode(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") } } } // renderSerialized renders contents with a serializer with status OK which you can change using RenderWithStatus or ctx.SetStatusCode(iris.StatusCode) func (ctx *Context) renderSerialized(contentType string, obj interface{}, options ...map[string]interface{}) error { s := ctx.framework.serializers finalResult, err := s.Serialize(contentType, obj, options...) if err != nil { return err } gzipEnabled := ctx.framework.Config.Gzip charset := ctx.framework.Config.Charset if len(options) > 0 { gzipEnabled = getGzipOption(gzipEnabled, options[0]) // located to the template.go below the RenderOptions charset = getCharsetOption(charset, options[0]) } ctype := contentType if ctype == contentMarkdown { // remember the text/markdown is just a custom internal iris content type, which in reallity renders html ctype = contentHTML } if ctype != contentBinary { // set the charset only on non-binary data ctype += "; charset=" + charset } ctx.SetContentType(ctype) if gzipEnabled && ctx.clientAllowsGzip() { _, err := fasthttp.WriteGzip(ctx.RequestCtx.Response.BodyWriter(), finalResult) if err != nil { return err } ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader) ctx.SetHeader(contentEncodingHeader, "gzip") } else { ctx.Response.SetBody(finalResult) } ctx.SetStatusCode(StatusOK) return nil } // RenderTemplateSource serves a template source(raw string contents) from the first template engines which supports raw parsing returns its result as string func (ctx *Context) RenderTemplateSource(status int, src string, binding interface{}, options ...map[string]interface{}) error { err := ctx.framework.templates.renderSource(ctx, src, binding, options...) if err == nil { ctx.SetStatusCode(status) } return err } // RenderWithStatus builds up the response from the specified template or a serialize engine. // Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engines func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, options ...map[string]interface{}) (err error) { if strings.IndexByte(name, '.') > -1 { //we have template err = ctx.framework.templates.renderFile(ctx, name, binding, options...) } else { err = ctx.renderSerialized(name, binding, options...) } if err == nil { ctx.SetStatusCode(status) } return } // 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 serialize engine. // Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize 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 503 service unavailable http status with a (html) message if render failed // Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engine func (ctx *Context) MustRender(name string, binding interface{}, options ...map[string]interface{}) { if err := ctx.Render(name, binding, options...); err != nil { ctx.HTML(StatusServiceUnavailable, fmt.Sprintf("