2016-05-30 16:08:09 +02:00
package iris
import (
2016-10-25 14:58:18 +02:00
"bytes"
2016-06-14 07:45:40 +02:00
"encoding/json"
"encoding/xml"
"fmt"
"io"
2017-01-02 20:20:17 +01:00
"io/ioutil"
"mime/multipart"
2016-06-14 07:45:40 +02:00
"net"
2017-01-02 20:20:17 +01:00
"net/http"
2016-06-14 07:45:40 +02:00
"os"
"path"
2016-05-30 16:08:09 +02:00
"reflect"
2016-10-27 02:17:09 +02:00
"regexp"
2016-05-30 16:08:09 +02:00
"runtime"
2016-06-14 07:45:40 +02:00
"strconv"
"strings"
2017-02-14 04:54:11 +01:00
"sync"
2016-06-14 07:45:40 +02:00
"time"
2016-10-02 13:59:04 +02:00
"github.com/iris-contrib/formBinder"
"github.com/kataras/go-errors"
2016-05-30 16:08:09 +02:00
)
const (
// ContentType represents the header["Content-Type"]
2016-06-14 07:45:40 +02:00
contentType = "Content-Type"
2016-05-30 16:08:09 +02:00
// ContentLength represents the header["Content-Length"]
2016-06-14 07:45:40 +02:00
contentLength = "Content-Length"
2016-08-14 04:44:36 +02:00
// 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"
2016-05-30 16:08:09 +02:00
// ContentHTML is the string of text/html response headers
2016-06-14 07:45:40 +02:00
contentHTML = "text/html"
Update to 4.0.0-alpha.3 - Response Engines, 'inject' the context.JSON/JSONP/Text/Data/Markdown/Render, Read HISTORY.md
## 4.0.0-alpha.2 -> 4.0.0-alpha.3
**New**
A **Response Engine** gives you the freedom to create/change the
render/response writer for
- `context.JSON`
- `context.JSONP`
- `context.XML`
- `context.Text`
- `context.Markdown`
- `context.Data`
- `context.Render("my_custom_type",mystructOrData{},
iris.RenderOptions{"gzip":false,"charset":"UTF-8"})`
- `context.MarkdownString`
- `iris.ResponseString(...)`
**Fix**
- https://github.com/kataras/iris/issues/294
**Small changes**
- `iris.Config.Charset`, before alpha.3 was `iris.Config.Rest.Charset` &
`iris.Config.Render.Template.Charset`, but you can override it at
runtime by passinth a map `iris.RenderOptions` on the `context.Render`
call .
- `iris.Config.IsDevelopment` , before alpha.1 was
`iris.Config.Render.Template.IsDevelopment`
**Websockets changes**
No need to import the `github.com/kataras/iris/websocket` to use the
`Connection` iteral, the websocket moved inside `kataras/iris` , now all
exported variables' names have the prefix of `Websocket`, so the old
`websocket.Connection` is now `iris.WebsocketConnection`.
Generally, no other changes on the 'frontend API', for response engines
examples and how you can register your own to add more features on
existing response engines or replace them, look
[here](https://github.com/iris-contrib/response).
**BAD SIDE**: E-Book is still pointing on the v3 release, but will be
updated soon.
2016-07-18 16:40:42 +02:00
// ContentBinary header value for binary data.
contentBinary = "application/octet-stream"
// ContentJSON header value for JSON data.
contentJSON = "application/json"
2016-09-01 05:01:53 +02:00
// ContentJSONP header value for JSONP & Javascript data.
Update to 4.0.0-alpha.3 - Response Engines, 'inject' the context.JSON/JSONP/Text/Data/Markdown/Render, Read HISTORY.md
## 4.0.0-alpha.2 -> 4.0.0-alpha.3
**New**
A **Response Engine** gives you the freedom to create/change the
render/response writer for
- `context.JSON`
- `context.JSONP`
- `context.XML`
- `context.Text`
- `context.Markdown`
- `context.Data`
- `context.Render("my_custom_type",mystructOrData{},
iris.RenderOptions{"gzip":false,"charset":"UTF-8"})`
- `context.MarkdownString`
- `iris.ResponseString(...)`
**Fix**
- https://github.com/kataras/iris/issues/294
**Small changes**
- `iris.Config.Charset`, before alpha.3 was `iris.Config.Rest.Charset` &
`iris.Config.Render.Template.Charset`, but you can override it at
runtime by passinth a map `iris.RenderOptions` on the `context.Render`
call .
- `iris.Config.IsDevelopment` , before alpha.1 was
`iris.Config.Render.Template.IsDevelopment`
**Websockets changes**
No need to import the `github.com/kataras/iris/websocket` to use the
`Connection` iteral, the websocket moved inside `kataras/iris` , now all
exported variables' names have the prefix of `Websocket`, so the old
`websocket.Connection` is now `iris.WebsocketConnection`.
Generally, no other changes on the 'frontend API', for response engines
examples and how you can register your own to add more features on
existing response engines or replace them, look
[here](https://github.com/iris-contrib/response).
**BAD SIDE**: E-Book is still pointing on the v3 release, but will be
updated soon.
2016-07-18 16:40:42 +02:00
contentJSONP = "application/javascript"
2016-09-01 05:01:53 +02:00
// ContentJavascript header value for Javascript/JSONP
// conversional
contentJavascript = "application/javascript"
Update to 4.0.0-alpha.3 - Response Engines, 'inject' the context.JSON/JSONP/Text/Data/Markdown/Render, Read HISTORY.md
## 4.0.0-alpha.2 -> 4.0.0-alpha.3
**New**
A **Response Engine** gives you the freedom to create/change the
render/response writer for
- `context.JSON`
- `context.JSONP`
- `context.XML`
- `context.Text`
- `context.Markdown`
- `context.Data`
- `context.Render("my_custom_type",mystructOrData{},
iris.RenderOptions{"gzip":false,"charset":"UTF-8"})`
- `context.MarkdownString`
- `iris.ResponseString(...)`
**Fix**
- https://github.com/kataras/iris/issues/294
**Small changes**
- `iris.Config.Charset`, before alpha.3 was `iris.Config.Rest.Charset` &
`iris.Config.Render.Template.Charset`, but you can override it at
runtime by passinth a map `iris.RenderOptions` on the `context.Render`
call .
- `iris.Config.IsDevelopment` , before alpha.1 was
`iris.Config.Render.Template.IsDevelopment`
**Websockets changes**
No need to import the `github.com/kataras/iris/websocket` to use the
`Connection` iteral, the websocket moved inside `kataras/iris` , now all
exported variables' names have the prefix of `Websocket`, so the old
`websocket.Connection` is now `iris.WebsocketConnection`.
Generally, no other changes on the 'frontend API', for response engines
examples and how you can register your own to add more features on
existing response engines or replace them, look
[here](https://github.com/iris-contrib/response).
**BAD SIDE**: E-Book is still pointing on the v3 release, but will be
updated soon.
2016-07-18 16:40:42 +02:00
// 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"
2016-05-30 16:08:09 +02:00
// LastModified "Last-Modified"
2016-06-14 07:45:40 +02:00
lastModified = "Last-Modified"
2016-05-30 16:08:09 +02:00
// IfModifiedSince "If-Modified-Since"
2016-06-14 07:45:40 +02:00
ifModifiedSince = "If-Modified-Since"
2016-05-30 16:08:09 +02:00
// ContentDisposition "Content-Disposition"
2016-06-14 07:45:40 +02:00
contentDisposition = "Content-Disposition"
2016-10-27 02:17:09 +02:00
// CacheControl "Cache-Control"
cacheControl = "Cache-Control"
2016-05-30 16:08:09 +02:00
2017-01-10 14:03:02 +01:00
// stopExecutionPosition used inside the Context, is the number which shows us that the context's middleware manually stop the execution
2016-05-30 16:08:09 +02:00
stopExecutionPosition = 255
)
2017-01-02 20:20:17 +01:00
type (
requestValue struct {
key [ ] byte
value interface { }
}
requestValues [ ] requestValue
)
func ( r * requestValues ) Set ( key string , value interface { } ) {
args := * r
n := len ( args )
for i := 0 ; i < n ; i ++ {
kv := & args [ i ]
if string ( kv . key ) == key {
kv . value = value
return
}
}
c := cap ( args )
if c > n {
args = args [ : n + 1 ]
kv := & args [ n ]
kv . key = append ( kv . key [ : 0 ] , key ... )
kv . value = value
* r = args
return
}
kv := requestValue { }
kv . key = append ( kv . key [ : 0 ] , key ... )
kv . value = value
* r = append ( args , kv )
}
func ( r * requestValues ) Get ( key string ) interface { } {
args := * r
n := len ( args )
for i := 0 ; i < n ; i ++ {
kv := & args [ i ]
if string ( kv . key ) == key {
return kv . value
}
}
return nil
}
func ( r * requestValues ) Reset ( ) {
* r = ( * r ) [ : 0 ]
}
2017-02-14 04:54:11 +01:00
type (
// ContextPool is a set of temporary *Context that may be individually saved and
// retrieved.
//
// Any item stored in the Pool may be removed automatically at any time without
// notification. If the Pool holds the only reference when this happens, the
// item might be deallocated.
//
// The ContextPool is safe for use by multiple goroutines simultaneously.
//
// ContextPool's purpose is to cache allocated but unused Contexts for later reuse,
// relieving pressure on the garbage collector.
ContextPool interface {
// Acquire returns a Context from pool.
// See Release.
Acquire ( w http . ResponseWriter , r * http . Request ) * Context
// Release puts a Context back to its pull, this function releases its resources.
// See Acquire.
Release ( ctx * Context )
// Framework is never used, except when you're in a place where you don't have access to the *iris.Framework station
// but you need to fire a func or check its Config.
//
// Used mostly inside external routers to take the .Config.VHost
// without the need of other param receivers and refactors when changes
//
// note: we could make a variable inside contextPool which would be received by newContextPool
// but really doesn't need, we just need to borrow a context: we are in pre-build state
// so the server is not actually running yet, no runtime performance cost.
Framework ( ) * Framework
// Run is a combination of Acquire and Release , between these two the `runner` runs,
// when `runner` finishes its job then the Context is being released.
Run ( w http . ResponseWriter , r * http . Request , runner func ( ctx * Context ) )
}
contextPool struct {
pool sync . Pool
}
)
var _ ContextPool = & contextPool { }
func ( c * contextPool ) Acquire ( w http . ResponseWriter , r * http . Request ) * Context {
ctx := c . pool . Get ( ) . ( * Context )
ctx . ResponseWriter = acquireResponseWriter ( w )
ctx . Request = r
return ctx
}
func ( c * contextPool ) Release ( ctx * Context ) {
// flush the body (on recorder) or just the status code (on basic response writer)
// when all finished
ctx . ResponseWriter . flushResponse ( )
ctx . Middleware = nil
ctx . session = nil
ctx . Request = nil
///TODO:
ctx . ResponseWriter . releaseMe ( )
ctx . values . Reset ( )
c . pool . Put ( ctx )
}
func ( c * contextPool ) Framework ( ) * Framework {
ctx := c . pool . Get ( ) . ( * Context )
s := ctx . framework
c . pool . Put ( ctx )
return s
}
func ( c * contextPool ) Run ( w http . ResponseWriter , r * http . Request , runner func ( * Context ) ) {
ctx := c . Acquire ( w , r )
runner ( ctx )
c . Release ( ctx )
}
2016-05-30 16:08:09 +02:00
type (
2016-07-13 13:59:37 +02:00
2016-05-31 10:05:42 +02:00
// Map is just a conversion for a map[string]interface{}
2016-07-08 19:41:50 +02:00
// should not be used inside Render when PongoEngine is used.
2016-05-30 16:08:09 +02:00
Map map [ string ] interface { }
2017-01-02 20:20:17 +01:00
2016-05-30 16:08:09 +02:00
// 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 {
2017-01-04 14:16:53 +01:00
ResponseWriter // *responseWriter by default, when record is on then *ResponseRecorder
2017-01-02 20:20:17 +01:00
Request * http . Request
values requestValues
framework * Framework
2017-01-10 14:03:02 +01:00
//keep track all registered middleware (handlers)
2016-10-13 16:25:01 +02:00
Middleware Middleware // exported because is useful for debugging
2017-02-15 19:06:19 +01:00
session Session
2016-10-13 16:25:01 +02:00
// Pos is the position number of the Context, look .Next to understand
2016-12-22 18:07:03 +01:00
Pos int // exported because is useful for debugging
2016-05-30 16:08:09 +02:00
}
)
2017-01-02 20:20:17 +01:00
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// -----------------------------Handler(s) Execution------------------------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
2016-05-30 16:08:09 +02:00
// Do calls the first handler only, it's like Next with negative pos, used only on Router&MemoryRouter
func ( ctx * Context ) Do ( ) {
2016-10-13 16:25:01 +02:00
ctx . Pos = 0
ctx . Middleware [ 0 ] . Serve ( ctx )
2016-05-30 16:08:09 +02:00
}
// Next calls all the next handler from the middleware stack, it used inside a middleware
func ( ctx * Context ) Next ( ) {
//set position to the next
2016-10-13 16:25:01 +02:00
ctx . Pos ++
2016-05-30 16:08:09 +02:00
//run the next
2017-01-02 20:20:17 +01:00
if ctx . Pos < len ( ctx . Middleware ) {
2016-10-13 16:25:01 +02:00
ctx . Middleware [ ctx . Pos ] . Serve ( ctx )
2016-05-30 16:08:09 +02:00
}
}
2017-02-05 17:13:24 +01:00
// NextHandler returns the next handler in the chain (ctx.Middleware)
// otherwise nil.
// Notes:
// If the result of NextHandler() will be executed then
// the ctx.Pos (which is exported for these reasons) should be manually increment(++)
// otherwise your app will visit twice the same handler.
func ( ctx * Context ) NextHandler ( ) Handler {
nextPos := ctx . Pos + 1
// check if it has a next middleware
if nextPos < len ( ctx . Middleware ) {
return ctx . Middleware [ nextPos ]
}
return nil
}
2016-05-30 16:08:09 +02:00
// StopExecution just sets the .pos to 255 in order to not move to the next middlewares(if any)
func ( ctx * Context ) StopExecution ( ) {
2016-10-13 16:25:01 +02:00
ctx . Pos = stopExecutionPosition
2016-05-30 16:08:09 +02:00
}
// 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 {
2016-10-13 16:25:01 +02:00
return ctx . Pos == stopExecutionPosition
2016-05-30 16:08:09 +02:00
}
// GetHandlerName as requested returns the stack-name of the function which the Middleware is setted from
func ( ctx * Context ) GetHandlerName ( ) string {
2016-10-13 16:25:01 +02:00
return runtime . FuncForPC ( reflect . ValueOf ( ctx . Middleware [ len ( ctx . Middleware ) - 1 ] ) . Pointer ( ) ) . Name ( )
2016-05-30 16:08:09 +02:00
}
2016-06-14 07:45:40 +02:00
2017-01-12 09:24:27 +01:00
// ExecRoute calls any route (mostly "offline" route) like it was requested by the user, but it is not.
2017-01-12 07:28:30 +01:00
// Offline means that the route is registered to the iris and have all features that a normal route has
// BUT it isn't available by browsing, its handlers executed only when other handler's context call them
// it can validate paths, has sessions, path parameters and all.
//
2017-02-18 06:03:37 +01:00
// You can find the Route by iris.Default.Routes().Lookup("theRouteName")
2017-02-14 04:54:11 +01:00
// you can set a route name as: myRoute := iris.Default.Get("/mypath", handler)("theRouteName")
2017-01-12 09:24:27 +01:00
// that will set a name to the route and returns its iris.Route instance for further usage.
//
2017-01-12 07:28:30 +01:00
// It doesn't changes the global state, if a route was "offline" it remains offline.
//
// see ExecRouteAgainst(routeName, againstRequestPath string),
2017-02-14 04:54:11 +01:00
// iris.Default.None(...) and iris.Default.SetRouteOnline/SetRouteOffline
2017-01-12 07:28:30 +01:00
// For more details look: https://github.com/kataras/iris/issues/585
//
// Example: https://github.com/iris-contrib/examples/tree/master/route_state
2017-02-18 06:03:37 +01:00
func ( ctx * Context ) ExecRoute ( r RouteInfo ) {
ctx . ExecRouteAgainst ( r , ctx . Path ( ) )
2017-01-12 07:28:30 +01:00
}
2017-01-12 09:24:27 +01:00
// ExecRouteAgainst calls any iris.Route against a 'virtually' request path
2017-01-12 07:28:30 +01:00
// like it was requested by the user, but it is not.
// Offline means that the route is registered to the iris and have all features that a normal route has
// BUT it isn't available by browsing, its handlers executed only when other handler's context call them
// it can validate paths, has sessions, path parameters and all.
//
2017-02-18 06:03:37 +01:00
// You can find the Route by iris.Default.Routes().Lookup("theRouteName")
2017-02-14 04:54:11 +01:00
// you can set a route name as: myRoute := iris.Default.Get("/mypath", handler)("theRouteName")
2017-01-12 09:24:27 +01:00
// that will set a name to the route and returns its iris.Route instance for further usage.
//
2017-01-12 07:28:30 +01:00
// It doesn't changes the global state, if a route was "offline" it remains offline.
//
// see ExecRoute(routeName),
2017-02-14 04:54:11 +01:00
// iris.Default.None(...) and iris.Default.SetRouteOnline/SetRouteOffline
2017-01-12 07:28:30 +01:00
// For more details look: https://github.com/kataras/iris/issues/585
//
// Example: https://github.com/iris-contrib/examples/tree/master/route_state
2017-02-18 06:03:37 +01:00
//
// User can get the response by simple using rec := ctx.Recorder(); rec.Body()/rec.StatusCode()/rec.Header()
// The route will be executed via the Router, as it would requested by client.
func ( ctx * Context ) ExecRouteAgainst ( r RouteInfo , againstRequestPath string ) {
if r != nil && againstRequestPath != "" {
// ok no need to clone the whole context, let's be dirty here for the sake of performance.
backupMidldeware := ctx . Middleware [ 0 : ]
backupPath := ctx . Path ( )
bakcupMethod := ctx . Method ( )
backupValues := ctx . values
backupPos := ctx . Pos
// sessions stays.
ctx . values . Reset ( )
ctx . Middleware = ctx . Middleware [ 0 : 0 ]
ctx . Request . RequestURI = againstRequestPath
ctx . Request . URL . Path = againstRequestPath
ctx . Request . Method = r . Method ( )
ctx . framework . Router . ServeHTTP ( ctx . ResponseWriter , ctx . Request )
ctx . Middleware = backupMidldeware
ctx . Request . RequestURI = backupPath
ctx . Request . URL . Path = backupPath
ctx . Request . Method = bakcupMethod
ctx . values = backupValues
ctx . Pos = backupPos
2017-01-12 07:28:30 +01:00
}
}
2017-01-12 09:24:27 +01:00
// Prioritize is a middleware which executes a route against this path
// when the request's Path has a prefix of the route's STATIC PART
// is not executing ExecRoute to determinate if it's valid, for performance reasons
// if this function is not enough for you and you want to test more than one parameterized path
// then use the: if c := ExecRoute(r); c == nil { /* move to the next, the route is not valid */ }
//
2017-02-18 06:03:37 +01:00
// You can find the Route by iris.Default.Routes().Lookup("theRouteName")
2017-02-14 04:54:11 +01:00
// you can set a route name as: myRoute := iris.Default.Get("/mypath", handler)("theRouteName")
2017-01-12 09:24:27 +01:00
// that will set a name to the route and returns its iris.Route instance for further usage.
//
// if the route found then it executes that and don't continue to the next handler
// if not found then continue to the next handler
2017-02-14 04:54:11 +01:00
func Prioritize ( r RouteInfo ) HandlerFunc {
2017-01-12 09:24:27 +01:00
if r != nil {
return func ( ctx * Context ) {
reqPath := ctx . Path ( )
2017-02-14 04:54:11 +01:00
staticPath := ctx . framework . policies . RouterReversionPolicy . StaticPath ( r . Path ( ) )
if strings . HasPrefix ( reqPath , staticPath ) {
2017-02-18 06:03:37 +01:00
ctx . ExecRouteAgainst ( r , reqPath ) // returns 404 page from EmitErrors, these things depends on router adaptors
// we are done here.
2017-01-12 09:24:27 +01:00
return
}
// execute the next handler if no prefix
// here look, the only error we catch is the 404,
// we can't go ctx.Next() and believe that the next handler will manage the error
// because it will not, we are not on the router.
ctx . Next ( )
}
}
return func ( ctx * Context ) { ctx . Next ( ) }
}
2017-01-02 20:20:17 +01:00
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// -----------------------------Request URL, Method, IP & Headers getters---------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
2016-06-14 07:45:40 +02:00
2017-01-02 20:20:17 +01:00
// Method returns the http request method
// same as *http.Request.Method
func ( ctx * Context ) Method ( ) string {
return ctx . Request . Method
2016-06-14 07:45:40 +02:00
}
2017-01-02 20:20:17 +01:00
// Host returns the host part of the current url
func ( ctx * Context ) Host ( ) string {
2017-01-04 18:50:54 +01:00
h := ctx . Request . URL . Host
if h == "" {
h = ctx . Request . Host
}
return h
2016-06-14 07:45:40 +02:00
}
2017-01-02 20:20:17 +01:00
// ServerHost returns the server host taken by *http.Request.Host
func ( ctx * Context ) ServerHost ( ) string {
return ctx . Request . Host
2016-06-26 07:44:10 +02:00
}
2017-01-02 20:20:17 +01:00
// Subdomain returns the subdomain (string) of this request, if any
func ( ctx * Context ) Subdomain ( ) ( subdomain string ) {
host := ctx . Host ( )
if index := strings . IndexByte ( host , '.' ) ; index > 0 {
subdomain = host [ 0 : index ]
}
2016-06-14 07:45:40 +02:00
2017-01-02 20:20:17 +01:00
return
2016-06-14 07:45:40 +02:00
}
2017-02-17 01:14:46 +01:00
// VirtualHostname returns the hostname that user registers,
// host path maybe differs from the real which is the Host(), which taken from a net.listener
2016-06-14 07:45:40 +02:00
func ( ctx * Context ) VirtualHostname ( ) string {
2017-01-02 20:20:17 +01:00
realhost := ctx . Host ( )
2016-06-30 04:58:04 +02:00
hostname := realhost
2017-02-14 04:54:11 +01:00
virtualhost := ctx . framework . Config . VHost
2016-06-30 04:58:04 +02:00
2016-06-14 07:45:40 +02:00
if portIdx := strings . IndexByte ( hostname , ':' ) ; portIdx > 0 {
hostname = hostname [ 0 : portIdx ]
}
2016-06-30 04:58:04 +02:00
if idxDotAnd := strings . LastIndexByte ( hostname , '.' ) ; idxDotAnd > 0 {
s := hostname [ idxDotAnd : ]
2016-07-20 23:03:36 +02:00
// 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
2016-06-30 04:58:04 +02:00
if s == ".1" {
hostname = strings . Replace ( hostname , "127.0.0.1" , virtualhost , 1 )
2016-07-20 23:03:36 +02:00
} else if s == ".0" {
hostname = strings . Replace ( hostname , "0.0.0.0" , virtualhost , 1 )
2016-06-30 04:58:04 +02:00
}
2016-07-20 23:03:36 +02:00
//
2016-06-30 04:58:04 +02:00
} else {
hostname = strings . Replace ( hostname , "localhost" , virtualhost , 1 )
}
2016-06-14 07:45:40 +02:00
return hostname
}
2017-01-02 20:20:17 +01:00
// Path returns the full escaped path as string
2016-06-14 07:45:40 +02:00
// for unescaped use: ctx.RequestCtx.RequestURI() or RequestPath(escape bool)
2017-01-02 20:20:17 +01:00
func ( ctx * Context ) Path ( ) string {
2017-01-10 07:54:39 +01:00
return ctx . RequestPath ( ctx . framework . Config . EnablePathEscape )
2016-06-14 07:45:40 +02:00
}
// RequestPath returns the requested path
func ( ctx * Context ) RequestPath ( escape bool ) string {
if escape {
2017-01-10 13:21:49 +01:00
// NOTE: for example:
// DecodeURI decodes %2F to '/'
// DecodeQuery decodes any %20 to whitespace
// here we choose to be query-decoded only
// and with context.ParamDecoded the user receives a URI decoded path parameter.
// see https://github.com/iris-contrib/examples/tree/master/named_parameters_pathescape
// and https://github.com/iris-contrib/examples/tree/master/pathescape
2017-01-10 07:54:39 +01:00
return DecodeQuery ( ctx . Request . URL . EscapedPath ( ) )
2016-06-14 07:45:40 +02:00
}
2017-01-10 07:54:39 +01:00
return ctx . Request . URL . Path
2016-06-14 07:45:40 +02:00
}
2017-01-02 20:20:17 +01:00
// RemoteAddr tries to return the real client's request IP
2016-06-14 07:45:40 +02:00
func ( ctx * Context ) RemoteAddr ( ) string {
2017-01-02 20:20:17 +01:00
header := ctx . RequestHeader ( "X-Real-Ip" )
2016-06-14 07:45:40 +02:00
realIP := strings . TrimSpace ( header )
if realIP != "" {
return realIP
}
2017-01-02 20:20:17 +01:00
realIP = ctx . RequestHeader ( "X-Forwarded-For" )
2016-06-14 07:45:40 +02:00
idx := strings . IndexByte ( realIP , ',' )
if idx >= 0 {
realIP = realIP [ 0 : idx ]
}
realIP = strings . TrimSpace ( realIP )
if realIP != "" {
return realIP
}
2017-01-02 20:20:17 +01:00
addr := strings . TrimSpace ( ctx . Request . RemoteAddr )
if len ( addr ) == 0 {
return ""
}
// if addr has port use the net.SplitHostPort otherwise(error occurs) take as it is
if ip , _ , err := net . SplitHostPort ( addr ) ; err == nil {
return ip
}
return addr
2016-06-14 07:45:40 +02:00
}
// 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 {
2017-01-02 20:20:17 +01:00
return ctx . Request . Header . Get ( k )
2016-06-14 07:45:40 +02:00
}
2016-09-15 17:59:27 +02:00
// IsAjax returns true if this request is an 'ajax request'( XMLHttpRequest)
//
// Read more at: http://www.w3schools.com/ajax/
2016-09-15 18:13:39 +02:00
func ( ctx * Context ) IsAjax ( ) bool {
return ctx . RequestHeader ( "HTTP_X_REQUESTED_WITH" ) == "XMLHttpRequest"
2016-09-15 17:59:27 +02:00
}
2017-01-02 20:20:17 +01:00
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// -----------------------------GET & POST arguments------------------------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
2016-06-14 07:45:40 +02:00
2017-01-02 20:20:17 +01:00
// URLParam returns the get parameter from a request , if any
func ( ctx * Context ) URLParam ( key string ) string {
return ctx . Request . URL . Query ( ) . Get ( key )
2016-06-14 07:45:40 +02:00
}
2017-01-10 14:03:02 +01:00
// URLParams returns a map of GET query parameters separated by comma if more than one
2017-01-02 20:20:17 +01:00
// it returns an empty map if nothing founds
func ( ctx * Context ) URLParams ( ) map [ string ] string {
values := map [ string ] string { }
q := ctx . URLParamsAsMulti ( )
if q != nil {
for k , v := range q {
values [ k ] = strings . Join ( v , "," )
}
2016-07-28 11:13:54 +02:00
}
2017-01-02 20:20:17 +01:00
return values
}
2016-07-28 11:13:54 +02:00
2017-01-02 20:20:17 +01:00
// URLParamsAsMulti returns a map of list contains the url get parameters
func ( ctx * Context ) URLParamsAsMulti ( ) map [ string ] [ ] string {
return ctx . Request . URL . Query ( )
}
2016-07-28 11:13:54 +02:00
2017-01-02 20:20:17 +01:00
// 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 ) )
}
2016-07-28 11:13:54 +02:00
2017-01-02 20:20:17 +01:00
// 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 )
}
func ( ctx * Context ) askParseForm ( ) error {
if ctx . Request . Form == nil {
if err := ctx . Request . ParseForm ( ) ; err != nil {
return err
2016-07-28 11:13:54 +02:00
}
2016-12-12 11:18:59 +01:00
}
2017-01-02 20:20:17 +01:00
return nil
2016-07-28 11:13:54 +02:00
}
2017-01-02 20:20:17 +01:00
// FormValues returns all post data values with their keys
// form data, get, post & put query arguments
//
// NOTE: A check for nil is necessary for zero results
func ( ctx * Context ) FormValues ( ) map [ string ] [ ] string {
// we skip the check of multipart form, takes too much memory, if user wants it can do manually now.
if err := ctx . askParseForm ( ) ; err != nil {
return nil
2016-07-28 11:13:54 +02:00
}
2017-01-02 20:20:17 +01:00
return ctx . Request . Form // nothing more to do, it's already contains both query and post & put args.
2016-07-28 11:13:54 +02:00
}
2017-01-02 20:20:17 +01:00
// FormValue returns a single form value by its name/key
func ( ctx * Context ) FormValue ( name string ) string {
return ctx . Request . FormValue ( name )
2016-07-28 11:13:54 +02:00
}
2017-01-02 20:20:17 +01:00
// PostValue returns a form's only-post value by its name
// same as Request.PostFormValue
func ( ctx * Context ) PostValue ( name string ) string {
return ctx . Request . PostFormValue ( name )
}
2016-06-14 07:45:40 +02:00
2017-01-02 20:20:17 +01:00
// FormFile returns the first file for the provided form key.
// FormFile calls ctx.Request.ParseMultipartForm and ParseForm if necessary.
//
// same as Request.FormFile
func ( ctx * Context ) FormFile ( key string ) ( multipart . File , * multipart . FileHeader , error ) {
return ctx . Request . FormFile ( key )
2016-06-14 07:45:40 +02:00
}
2017-01-05 17:59:15 +01:00
var (
errTemplateExecute = errors . New ( "Unable to execute a template. 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" )
)
2017-01-02 20:20:17 +01:00
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// -----------------------------Request Body Binders/Readers----------------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
2017-01-04 20:29:58 +01:00
// NOTE: No default max body size http package has some built'n protection for DoS attacks
2017-02-14 04:54:11 +01:00
// See iris.Default.Config.MaxBytesReader, https://github.com/golang/go/issues/2093#issuecomment-66057813
2017-01-04 20:29:58 +01:00
// and https://github.com/golang/go/issues/2093#issuecomment-66057824
// LimitRequestBodySize is a middleware which sets a request body size limit for all next handlers
// should be registered before all other handlers
var LimitRequestBodySize = func ( maxRequestBodySizeBytes int64 ) HandlerFunc {
return func ( ctx * Context ) {
ctx . SetMaxRequestBodySize ( maxRequestBodySizeBytes )
ctx . Next ( )
}
}
// SetMaxRequestBodySize sets a limit to the request body size
// should be called before reading the request body from the client
func ( ctx * Context ) SetMaxRequestBodySize ( limitOverBytes int64 ) {
ctx . Request . Body = http . MaxBytesReader ( ctx . ResponseWriter , ctx . Request . Body , limitOverBytes )
}
2016-10-06 22:19:55 +02:00
// 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
}
2016-12-12 11:18:59 +01:00
// Unmarshaler is the interface implemented by types that can unmarshal any raw data
// TIP INFO: Any v object which implements the BodyDecoder can be override the unmarshaler
type Unmarshaler interface {
Unmarshal ( data [ ] byte , v interface { } ) error
}
2016-10-06 22:19:55 +02:00
2016-12-12 11:18:59 +01:00
// UnmarshalerFunc a shortcut for the Unmarshaler interface
//
// See 'Unmarshaler' and 'BodyDecoder' for more
type UnmarshalerFunc func ( data [ ] byte , v interface { } ) error
2016-06-14 07:45:40 +02:00
2016-12-12 11:18:59 +01:00
// Unmarshal parses the X-encoded data and stores the result in the value pointed to by v.
// Unmarshal uses the inverse of the encodings that Marshal uses, allocating maps,
// slices, and pointers as necessary.
func ( u UnmarshalerFunc ) Unmarshal ( data [ ] byte , v interface { } ) error {
return u ( data , v )
2016-06-14 07:45:40 +02:00
}
2016-12-12 11:18:59 +01:00
// UnmarshalBody reads the request's body and binds it to a value or pointer of any type
// Examples of usage: context.ReadJSON, context.ReadXML
func ( ctx * Context ) UnmarshalBody ( v interface { } , unmarshaler Unmarshaler ) error {
2017-01-02 20:20:17 +01:00
if ctx . Request . Body == nil {
2017-02-14 04:54:11 +01:00
return errors . New ( "unmarshal: empty body" )
2017-01-02 20:20:17 +01:00
}
rawData , err := ioutil . ReadAll ( ctx . Request . Body )
if err != nil {
return err
}
2016-10-06 22:19:55 +02:00
2017-01-30 11:35:43 +01:00
if ctx . framework . Config . DisableBodyConsumptionOnUnmarshal {
// * remember, Request.Body has no Bytes(), we have to consume them first
// and after re-set them to the body, this is the only solution.
ctx . Request . Body = ioutil . NopCloser ( bytes . NewBuffer ( rawData ) )
}
2016-12-12 11:18:59 +01:00
// check if the v contains its own decode
// in this case the v should be a pointer also,
2016-10-06 22:19:55 +02:00
// but this is up to the user's custom Decode implementation*
//
// See 'BodyDecoder' for more
2016-12-12 11:18:59 +01:00
if decoder , isDecoder := v . ( BodyDecoder ) ; isDecoder {
2016-10-06 22:19:55 +02:00
return decoder . Decode ( rawData )
2016-06-14 07:45:40 +02:00
}
2016-12-12 11:18:59 +01:00
// check if v is already a pointer, if yes then pass as it's
if reflect . TypeOf ( v ) . Kind ( ) == reflect . Ptr {
return unmarshaler . Unmarshal ( rawData , v )
2016-10-06 22:19:55 +02:00
}
2016-12-12 11:18:59 +01:00
// finally, if the v doesn't contains a self-body decoder and it's not a pointer
// use the custom unmarshaler to bind the body
return unmarshaler . Unmarshal ( rawData , & v )
}
// ReadJSON reads JSON from request's body and binds it to a value of any json-valid type
func ( ctx * Context ) ReadJSON ( jsonObject interface { } ) error {
return ctx . UnmarshalBody ( jsonObject , UnmarshalerFunc ( json . Unmarshal ) )
}
// ReadXML reads XML from request's body and binds it to a value of any xml-valid type
func ( ctx * Context ) ReadXML ( xmlObject interface { } ) error {
return ctx . UnmarshalBody ( xmlObject , UnmarshalerFunc ( xml . Unmarshal ) )
2016-06-14 07:45:40 +02:00
}
// ReadForm binds the formObject with the form data
// it supports any kind of struct
func ( ctx * Context ) ReadForm ( formObject interface { } ) error {
2017-01-02 20:20:17 +01:00
values := ctx . FormValues ( )
if values == nil {
return errors . New ( "An empty form passed on context.ReadForm" )
}
return errReadBody . With ( formBinder . Decode ( values , formObject ) )
}
2016-06-14 07:45:40 +02:00
/* Response */
// SetContentType sets the response writer's header key 'Content-Type' to a given value(s)
func ( ctx * Context ) SetContentType ( s string ) {
2017-01-02 20:20:17 +01:00
ctx . ResponseWriter . Header ( ) . Set ( contentType , s )
2016-06-14 07:45:40 +02:00
}
2017-01-02 20:20:17 +01:00
// SetHeader write to the response writer's header to a given key the given value
2016-06-14 07:45:40 +02:00
func ( ctx * Context ) SetHeader ( k string , v string ) {
2017-01-02 20:20:17 +01:00
ctx . ResponseWriter . Header ( ) . Add ( k , v )
}
// SetStatusCode sets the status code header to the response
//
2017-01-04 14:16:53 +01:00
// same as .WriteHeader, iris takes cares of your status code seriously
2017-01-02 20:20:17 +01:00
func ( ctx * Context ) SetStatusCode ( statusCode int ) {
ctx . ResponseWriter . WriteHeader ( statusCode )
2016-06-14 07:45:40 +02:00
}
// Redirect redirect sends a redirect response the client
// accepts 2 parameters string and an optional int
// first parameter is the url to redirect
2016-11-22 15:46:07 +01:00
// second parameter is the http status should send, default is 302 (StatusFound),
// you can set it to 301 (Permant redirect), if that's nessecery
2016-06-14 07:45:40 +02:00
func ( ctx * Context ) Redirect ( urlToRedirect string , statusHeader ... int ) {
2016-11-22 15:46:07 +01:00
ctx . StopExecution ( )
2016-09-30 17:48:48 +02:00
httpStatus := StatusFound // a 'temporary-redirect-like' which works better than for our purpose
2017-02-14 04:54:11 +01:00
if len ( statusHeader ) > 0 && statusHeader [ 0 ] > 0 {
2016-06-14 07:45:40 +02:00
httpStatus = statusHeader [ 0 ]
}
2016-08-18 02:20:59 +02:00
2017-03-09 13:41:08 +01:00
// we don't know the Method of the url to redirect,
// sure we can find it by reverse routing as we already implemented
// but it will take too much time for a simple redirect, it doesn't worth it.
// So we are checking the CURRENT Method for GET, HEAD, CONNECT and TRACE.
// the
// Fixes: http: //support.iris-go.com/d/21-wrong-warning-message-while-redirecting
shouldCheckForCycle := urlToRedirect == ctx . Path ( ) && ctx . Method ( ) == MethodGet
// from POST to GET on the same path will give a warning message but developers don't use the iris.DevLogger
// for production, so I assume it's OK to let it logs it
// (it can solve issues when developer redirects to the same handler over and over again)
// Note: it doesn't stops the redirect, the developer gets what he/she expected.
if shouldCheckForCycle {
ctx . Log ( DevMode , "warning: redirect from: '%s' to: '%s',\ncurrent method: '%s'" , ctx . Path ( ) , urlToRedirect , ctx . Method ( ) )
2016-11-22 15:46:07 +01:00
}
2017-03-09 13:41:08 +01:00
2017-01-04 14:16:53 +01:00
http . Redirect ( ctx . ResponseWriter , ctx . Request , urlToRedirect , httpStatus )
2016-06-14 07:45:40 +02:00
}
// 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 )
}
}
2017-01-02 20:20:17 +01:00
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// -----------------------------(Custom) Errors-----------------------------------------
// ----------------------Look iris.OnError/EmitError for more---------------------------
// -------------------------------------------------------------------------------------
2016-06-14 07:45:40 +02:00
// 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 ( )
}
2017-01-02 20:20:17 +01:00
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// -------------------------Context's gzip inline response writer ----------------------
2017-03-01 18:17:32 +01:00
// ---------------------Look adaptors/view & iris.go for more options-------------------
2017-01-02 20:20:17 +01:00
// -------------------------------------------------------------------------------------
var (
errClientDoesNotSupportGzip = errors . New ( "Client doesn't supports gzip compression" )
)
2016-08-14 04:44:36 +02:00
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
}
2017-01-02 20:20:17 +01:00
// WriteGzip accepts bytes, which are compressed to gzip format and sent to the client.
// returns the number of bytes written and an error ( if the client doesn' supports gzip compression)
func ( ctx * Context ) WriteGzip ( b [ ] byte ) ( int , error ) {
2016-08-14 04:44:36 +02:00
if ctx . clientAllowsGzip ( ) {
2017-01-02 20:20:17 +01:00
ctx . ResponseWriter . Header ( ) . Add ( varyHeader , acceptEncodingHeader )
2017-03-01 18:17:32 +01:00
gzipWriter := acquireGzipWriter ( ctx . ResponseWriter )
2017-01-02 20:20:17 +01:00
n , err := gzipWriter . Write ( b )
2017-03-01 18:17:32 +01:00
releaseGzipWriter ( gzipWriter )
2017-01-02 20:20:17 +01:00
2016-08-14 04:44:36 +02:00
if err == nil {
ctx . SetHeader ( contentEncodingHeader , "gzip" )
2017-01-02 20:20:17 +01:00
} // else write the contents as it is? no let's create a new func for this
return n , err
}
2017-01-05 17:59:15 +01:00
2017-01-02 20:20:17 +01:00
return 0 , errClientDoesNotSupportGzip
}
// TryWriteGzip accepts bytes, which are compressed to gzip format and sent to the client.
// If client does not supprots gzip then the contents are written as they are, uncompressed.
func ( ctx * Context ) TryWriteGzip ( b [ ] byte ) ( int , error ) {
n , err := ctx . WriteGzip ( b )
if err != nil {
// check if the error came from gzip not allowed and not the writer itself
if _ , ok := err . ( * errors . Error ) ; ok {
// client didn't supported gzip, write them uncompressed:
return ctx . ResponseWriter . Write ( b )
2016-08-14 04:44:36 +02:00
}
2016-07-25 13:45:12 +02:00
}
2017-01-02 20:20:17 +01:00
return n , err
2016-06-14 07:45:40 +02:00
}
2017-01-02 20:20:17 +01:00
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// -----------------------------Render and powerful content negotiation-----------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
2017-02-14 04:54:11 +01:00
const (
// NoLayout to disable layout for a particular template file
2017-03-01 18:17:32 +01:00
NoLayout = "@.|.@no_layout@.|.@"
2017-02-14 04:54:11 +01:00
// TemplateLayoutContextKey is the name of the user values which can be used to set a template layout from a middleware and override the parent's
TemplateLayoutContextKey = "templateLayout"
)
// getGzipOption receives a default value and the render options map and returns if gzip is enabled for this render action
func getGzipOption ( defaultValue bool , options map [ string ] interface { } ) bool {
gzipOpt := options [ "gzip" ] // we only need that, so don't create new map to keep the options.
if b , isBool := gzipOpt . ( bool ) ; isBool {
return b
2016-09-10 06:23:02 +02:00
}
2017-02-14 04:54:11 +01:00
return defaultValue
}
// gtCharsetOption receives a default value and the render options map and returns the correct charset for this render action
func getCharsetOption ( defaultValue string , options map [ string ] interface { } ) string {
charsetOpt := options [ "charset" ]
if s , isString := charsetOpt . ( string ) ; isString {
return s
2016-09-10 06:23:02 +02:00
}
2017-02-14 04:54:11 +01:00
return defaultValue
}
2016-09-10 06:23:02 +02:00
2017-02-14 04:54:11 +01:00
func ( ctx * Context ) fastRenderWithStatus ( status int , cType string , data [ ] byte ) ( err error ) {
if _ , shouldFirstStatusCode := ctx . ResponseWriter . ( * responseWriter ) ; shouldFirstStatusCode {
ctx . SetStatusCode ( status )
2016-09-10 06:23:02 +02:00
}
2017-02-14 04:54:11 +01:00
gzipEnabled := ctx . framework . Config . Gzip
charset := ctx . framework . Config . Charset
2016-09-10 06:23:02 +02:00
2017-02-14 04:54:11 +01:00
if cType != contentBinary {
cType += "; charset=" + charset
2016-09-10 06:23:02 +02:00
}
2017-02-14 04:54:11 +01:00
// add the content type to the response
ctx . SetContentType ( cType )
var out io . Writer
if gzipEnabled && ctx . clientAllowsGzip ( ) {
ctx . ResponseWriter . Header ( ) . Add ( varyHeader , acceptEncodingHeader )
ctx . SetHeader ( contentEncodingHeader , "gzip" )
2017-03-01 18:17:32 +01:00
gzipWriter := acquireGzipWriter ( ctx . ResponseWriter )
defer releaseGzipWriter ( gzipWriter )
2017-02-14 04:54:11 +01:00
out = gzipWriter
2016-09-10 06:23:02 +02:00
} else {
2017-02-14 04:54:11 +01:00
out = ctx . ResponseWriter
2016-09-10 06:23:02 +02:00
}
2017-02-14 04:54:11 +01:00
// no need to loop through the RenderPolicy, these types must be fast as possible
// with the features like gzip and custom charset too.
_ , err = out . Write ( data )
2016-09-09 07:09:03 +02:00
2017-02-14 04:54:11 +01:00
// we don't care for the last one it will not be written more than one if we have the *responseWriter
///TODO:
// if we have ResponseRecorder order doesn't matters but I think the transactions have bugs,
// temporary let's keep it here because it 'fixes' one of them...
ctx . SetStatusCode ( status )
return
2016-09-09 07:09:03 +02:00
}
2016-09-10 06:23:02 +02:00
// 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
2016-09-09 07:09:03 +02:00
func ( ctx * Context ) RenderWithStatus ( status int , name string , binding interface { } , options ... map [ string ] interface { } ) ( err error ) {
2017-01-04 14:16:53 +01:00
if _ , shouldFirstStatusCode := ctx . ResponseWriter . ( * responseWriter ) ; shouldFirstStatusCode {
ctx . SetStatusCode ( status )
}
2017-02-14 04:54:11 +01:00
// we do all these because we don't want to initialize a new map for each execution...
gzipEnabled := ctx . framework . Config . Gzip
charset := ctx . framework . Config . Charset
if len ( options ) > 0 {
gzipEnabled = getGzipOption ( gzipEnabled , options [ 0 ] )
charset = getCharsetOption ( charset , options [ 0 ] )
}
ctxLayout := ctx . GetString ( TemplateLayoutContextKey )
if ctxLayout != "" {
if len ( options ) > 0 {
options [ 0 ] [ "layout" ] = ctxLayout
} else {
options = [ ] map [ string ] interface { } { { "layout" : ctxLayout } }
}
}
// Find Content type
// if it the name is not a template file, then take that as the content type.
cType := contentHTML
if ! strings . Contains ( name , "." ) {
// remember the text/markdown is just a custom internal
// iris content type, which in reallity renders html
if name != contentMarkdown {
cType = name
}
}
if cType != contentBinary {
cType += "; charset=" + charset
}
// add the content type to the response
ctx . SetContentType ( cType )
var out io . Writer
if gzipEnabled && ctx . clientAllowsGzip ( ) {
ctx . ResponseWriter . Header ( ) . Add ( varyHeader , acceptEncodingHeader )
ctx . SetHeader ( contentEncodingHeader , "gzip" )
2017-03-01 18:17:32 +01:00
gzipWriter := acquireGzipWriter ( ctx . ResponseWriter )
defer releaseGzipWriter ( gzipWriter )
2017-02-14 04:54:11 +01:00
out = gzipWriter
2016-09-10 06:23:02 +02:00
} else {
2017-02-14 04:54:11 +01:00
out = ctx . ResponseWriter
Update to 4.0.0-alpha.3 - Response Engines, 'inject' the context.JSON/JSONP/Text/Data/Markdown/Render, Read HISTORY.md
## 4.0.0-alpha.2 -> 4.0.0-alpha.3
**New**
A **Response Engine** gives you the freedom to create/change the
render/response writer for
- `context.JSON`
- `context.JSONP`
- `context.XML`
- `context.Text`
- `context.Markdown`
- `context.Data`
- `context.Render("my_custom_type",mystructOrData{},
iris.RenderOptions{"gzip":false,"charset":"UTF-8"})`
- `context.MarkdownString`
- `iris.ResponseString(...)`
**Fix**
- https://github.com/kataras/iris/issues/294
**Small changes**
- `iris.Config.Charset`, before alpha.3 was `iris.Config.Rest.Charset` &
`iris.Config.Render.Template.Charset`, but you can override it at
runtime by passinth a map `iris.RenderOptions` on the `context.Render`
call .
- `iris.Config.IsDevelopment` , before alpha.1 was
`iris.Config.Render.Template.IsDevelopment`
**Websockets changes**
No need to import the `github.com/kataras/iris/websocket` to use the
`Connection` iteral, the websocket moved inside `kataras/iris` , now all
exported variables' names have the prefix of `Websocket`, so the old
`websocket.Connection` is now `iris.WebsocketConnection`.
Generally, no other changes on the 'frontend API', for response engines
examples and how you can register your own to add more features on
existing response engines or replace them, look
[here](https://github.com/iris-contrib/response).
**BAD SIDE**: E-Book is still pointing on the v3 release, but will be
updated soon.
2016-07-18 16:40:42 +02:00
}
2017-02-14 04:54:11 +01:00
err = ctx . framework . Render ( out , name , binding , options ... )
2017-01-04 14:16:53 +01:00
// we don't care for the last one it will not be written more than one if we have the *responseWriter
///TODO:
// if we have ResponseRecorder order doesn't matters but I think the transactions have bugs , for now let's keep it here because it 'fixes' one of them...
ctx . SetStatusCode ( status )
2016-09-09 07:09:03 +02:00
return
2016-06-14 07:45:40 +02:00
}
Update to 4.0.0-alpha.3 - Response Engines, 'inject' the context.JSON/JSONP/Text/Data/Markdown/Render, Read HISTORY.md
## 4.0.0-alpha.2 -> 4.0.0-alpha.3
**New**
A **Response Engine** gives you the freedom to create/change the
render/response writer for
- `context.JSON`
- `context.JSONP`
- `context.XML`
- `context.Text`
- `context.Markdown`
- `context.Data`
- `context.Render("my_custom_type",mystructOrData{},
iris.RenderOptions{"gzip":false,"charset":"UTF-8"})`
- `context.MarkdownString`
- `iris.ResponseString(...)`
**Fix**
- https://github.com/kataras/iris/issues/294
**Small changes**
- `iris.Config.Charset`, before alpha.3 was `iris.Config.Rest.Charset` &
`iris.Config.Render.Template.Charset`, but you can override it at
runtime by passinth a map `iris.RenderOptions` on the `context.Render`
call .
- `iris.Config.IsDevelopment` , before alpha.1 was
`iris.Config.Render.Template.IsDevelopment`
**Websockets changes**
No need to import the `github.com/kataras/iris/websocket` to use the
`Connection` iteral, the websocket moved inside `kataras/iris` , now all
exported variables' names have the prefix of `Websocket`, so the old
`websocket.Connection` is now `iris.WebsocketConnection`.
Generally, no other changes on the 'frontend API', for response engines
examples and how you can register your own to add more features on
existing response engines or replace them, look
[here](https://github.com/iris-contrib/response).
**BAD SIDE**: E-Book is still pointing on the v3 release, but will be
updated soon.
2016-07-18 16:40:42 +02:00
// Render same as .RenderWithStatus but with status to iris.StatusOK (200) if no previous status exists
2016-09-10 06:23:02 +02:00
// 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
2016-07-13 05:28:09 +02:00
func ( ctx * Context ) Render ( name string , binding interface { } , options ... map [ string ] interface { } ) error {
2017-01-02 20:20:17 +01:00
errCode := ctx . ResponseWriter . StatusCode ( )
2016-07-06 20:24:34 +02:00
if errCode <= 0 {
errCode = StatusOK
}
2016-07-13 05:28:09 +02:00
return ctx . RenderWithStatus ( errCode , name , binding , options ... )
2016-06-14 07:45:40 +02:00
}
2016-10-15 19:57:52 +02:00
// MustRender same as .Render but returns 503 service unavailable http status with a (html) message if render failed
2016-09-10 06:23:02 +02:00
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engine
2016-07-13 05:28:09 +02:00
func ( ctx * Context ) MustRender ( name string , binding interface { } , options ... map [ string ] interface { } ) {
if err := ctx . Render ( name , binding , options ... ) ; err != nil {
2017-02-14 04:54:11 +01:00
htmlErr := ctx . HTML ( StatusServiceUnavailable ,
fmt . Sprintf ( "<h2>Template: %s</h2><b>%s</b>" , name , err . Error ( ) ) )
ctx . Log ( DevMode , "MustRender failed to render '%s', trace: %s\n" ,
name , err )
if htmlErr != nil {
ctx . Log ( DevMode , "MustRender also failed to render the html fallback: %s" ,
htmlErr . Error ( ) )
2016-09-07 06:36:23 +02:00
}
2017-02-14 04:54:11 +01:00
2016-06-14 07:45:40 +02:00
}
}
2017-02-14 04:54:11 +01:00
// Data writes out the raw bytes as binary data.
//
// RenderPolicy does NOT apply to context.HTML, context.Text and context.Data
// To change their default behavior users should use
// the context.RenderWithStatus(statusCode, contentType, content, options...) instead.
func ( ctx * Context ) Data ( status int , data [ ] byte ) error {
return ctx . fastRenderWithStatus ( status , contentBinary , data )
2016-06-14 07:45:40 +02:00
}
2017-02-14 04:54:11 +01:00
// Text writes out a string as plain text.
//
// RenderPolicy does NOT apply to context.HTML, context.Text and context.Data
// To change their default behavior users should use
// the context.RenderWithStatus(statusCode, contentType, content, options...) instead.
func ( ctx * Context ) Text ( status int , text string ) error {
2017-02-21 12:26:30 +01:00
return ctx . fastRenderWithStatus ( status , contentText , [ ] byte ( text ) )
Update to 4.0.0-alpha.3 - Response Engines, 'inject' the context.JSON/JSONP/Text/Data/Markdown/Render, Read HISTORY.md
## 4.0.0-alpha.2 -> 4.0.0-alpha.3
**New**
A **Response Engine** gives you the freedom to create/change the
render/response writer for
- `context.JSON`
- `context.JSONP`
- `context.XML`
- `context.Text`
- `context.Markdown`
- `context.Data`
- `context.Render("my_custom_type",mystructOrData{},
iris.RenderOptions{"gzip":false,"charset":"UTF-8"})`
- `context.MarkdownString`
- `iris.ResponseString(...)`
**Fix**
- https://github.com/kataras/iris/issues/294
**Small changes**
- `iris.Config.Charset`, before alpha.3 was `iris.Config.Rest.Charset` &
`iris.Config.Render.Template.Charset`, but you can override it at
runtime by passinth a map `iris.RenderOptions` on the `context.Render`
call .
- `iris.Config.IsDevelopment` , before alpha.1 was
`iris.Config.Render.Template.IsDevelopment`
**Websockets changes**
No need to import the `github.com/kataras/iris/websocket` to use the
`Connection` iteral, the websocket moved inside `kataras/iris` , now all
exported variables' names have the prefix of `Websocket`, so the old
`websocket.Connection` is now `iris.WebsocketConnection`.
Generally, no other changes on the 'frontend API', for response engines
examples and how you can register your own to add more features on
existing response engines or replace them, look
[here](https://github.com/iris-contrib/response).
**BAD SIDE**: E-Book is still pointing on the v3 release, but will be
updated soon.
2016-07-18 16:40:42 +02:00
}
2017-02-14 04:54:11 +01:00
// HTML writes html string with a http status
//
// RenderPolicy does NOT apply to context.HTML, context.Text and context.Data
// To change their default behavior users should use
// the context.RenderWithStatus(statusCode, contentType, content, options...) instead.
func ( ctx * Context ) HTML ( status int , htmlContents string ) error {
return ctx . fastRenderWithStatus ( status , contentHTML , [ ] byte ( htmlContents ) )
Update to 4.0.0-alpha.3 - Response Engines, 'inject' the context.JSON/JSONP/Text/Data/Markdown/Render, Read HISTORY.md
## 4.0.0-alpha.2 -> 4.0.0-alpha.3
**New**
A **Response Engine** gives you the freedom to create/change the
render/response writer for
- `context.JSON`
- `context.JSONP`
- `context.XML`
- `context.Text`
- `context.Markdown`
- `context.Data`
- `context.Render("my_custom_type",mystructOrData{},
iris.RenderOptions{"gzip":false,"charset":"UTF-8"})`
- `context.MarkdownString`
- `iris.ResponseString(...)`
**Fix**
- https://github.com/kataras/iris/issues/294
**Small changes**
- `iris.Config.Charset`, before alpha.3 was `iris.Config.Rest.Charset` &
`iris.Config.Render.Template.Charset`, but you can override it at
runtime by passinth a map `iris.RenderOptions` on the `context.Render`
call .
- `iris.Config.IsDevelopment` , before alpha.1 was
`iris.Config.Render.Template.IsDevelopment`
**Websockets changes**
No need to import the `github.com/kataras/iris/websocket` to use the
`Connection` iteral, the websocket moved inside `kataras/iris` , now all
exported variables' names have the prefix of `Websocket`, so the old
`websocket.Connection` is now `iris.WebsocketConnection`.
Generally, no other changes on the 'frontend API', for response engines
examples and how you can register your own to add more features on
existing response engines or replace them, look
[here](https://github.com/iris-contrib/response).
**BAD SIDE**: E-Book is still pointing on the v3 release, but will be
updated soon.
2016-07-18 16:40:42 +02:00
}
2016-06-14 07:45:40 +02:00
// JSON marshals the given interface object and writes the JSON response.
func ( ctx * Context ) JSON ( status int , v interface { } ) error {
Update to 4.0.0-alpha.3 - Response Engines, 'inject' the context.JSON/JSONP/Text/Data/Markdown/Render, Read HISTORY.md
## 4.0.0-alpha.2 -> 4.0.0-alpha.3
**New**
A **Response Engine** gives you the freedom to create/change the
render/response writer for
- `context.JSON`
- `context.JSONP`
- `context.XML`
- `context.Text`
- `context.Markdown`
- `context.Data`
- `context.Render("my_custom_type",mystructOrData{},
iris.RenderOptions{"gzip":false,"charset":"UTF-8"})`
- `context.MarkdownString`
- `iris.ResponseString(...)`
**Fix**
- https://github.com/kataras/iris/issues/294
**Small changes**
- `iris.Config.Charset`, before alpha.3 was `iris.Config.Rest.Charset` &
`iris.Config.Render.Template.Charset`, but you can override it at
runtime by passinth a map `iris.RenderOptions` on the `context.Render`
call .
- `iris.Config.IsDevelopment` , before alpha.1 was
`iris.Config.Render.Template.IsDevelopment`
**Websockets changes**
No need to import the `github.com/kataras/iris/websocket` to use the
`Connection` iteral, the websocket moved inside `kataras/iris` , now all
exported variables' names have the prefix of `Websocket`, so the old
`websocket.Connection` is now `iris.WebsocketConnection`.
Generally, no other changes on the 'frontend API', for response engines
examples and how you can register your own to add more features on
existing response engines or replace them, look
[here](https://github.com/iris-contrib/response).
**BAD SIDE**: E-Book is still pointing on the v3 release, but will be
updated soon.
2016-07-18 16:40:42 +02:00
return ctx . RenderWithStatus ( status , contentJSON , v )
2016-06-14 07:45:40 +02:00
}
// JSONP marshals the given interface object and writes the JSON response.
func ( ctx * Context ) JSONP ( status int , callback string , v interface { } ) error {
Update to 4.0.0-alpha.3 - Response Engines, 'inject' the context.JSON/JSONP/Text/Data/Markdown/Render, Read HISTORY.md
## 4.0.0-alpha.2 -> 4.0.0-alpha.3
**New**
A **Response Engine** gives you the freedom to create/change the
render/response writer for
- `context.JSON`
- `context.JSONP`
- `context.XML`
- `context.Text`
- `context.Markdown`
- `context.Data`
- `context.Render("my_custom_type",mystructOrData{},
iris.RenderOptions{"gzip":false,"charset":"UTF-8"})`
- `context.MarkdownString`
- `iris.ResponseString(...)`
**Fix**
- https://github.com/kataras/iris/issues/294
**Small changes**
- `iris.Config.Charset`, before alpha.3 was `iris.Config.Rest.Charset` &
`iris.Config.Render.Template.Charset`, but you can override it at
runtime by passinth a map `iris.RenderOptions` on the `context.Render`
call .
- `iris.Config.IsDevelopment` , before alpha.1 was
`iris.Config.Render.Template.IsDevelopment`
**Websockets changes**
No need to import the `github.com/kataras/iris/websocket` to use the
`Connection` iteral, the websocket moved inside `kataras/iris` , now all
exported variables' names have the prefix of `Websocket`, so the old
`websocket.Connection` is now `iris.WebsocketConnection`.
Generally, no other changes on the 'frontend API', for response engines
examples and how you can register your own to add more features on
existing response engines or replace them, look
[here](https://github.com/iris-contrib/response).
**BAD SIDE**: E-Book is still pointing on the v3 release, but will be
updated soon.
2016-07-18 16:40:42 +02:00
return ctx . RenderWithStatus ( status , contentJSONP , v , map [ string ] interface { } { "callback" : callback } )
2016-06-14 07:45:40 +02:00
}
// XML marshals the given interface object and writes the XML response.
func ( ctx * Context ) XML ( status int , v interface { } ) error {
Update to 4.0.0-alpha.3 - Response Engines, 'inject' the context.JSON/JSONP/Text/Data/Markdown/Render, Read HISTORY.md
## 4.0.0-alpha.2 -> 4.0.0-alpha.3
**New**
A **Response Engine** gives you the freedom to create/change the
render/response writer for
- `context.JSON`
- `context.JSONP`
- `context.XML`
- `context.Text`
- `context.Markdown`
- `context.Data`
- `context.Render("my_custom_type",mystructOrData{},
iris.RenderOptions{"gzip":false,"charset":"UTF-8"})`
- `context.MarkdownString`
- `iris.ResponseString(...)`
**Fix**
- https://github.com/kataras/iris/issues/294
**Small changes**
- `iris.Config.Charset`, before alpha.3 was `iris.Config.Rest.Charset` &
`iris.Config.Render.Template.Charset`, but you can override it at
runtime by passinth a map `iris.RenderOptions` on the `context.Render`
call .
- `iris.Config.IsDevelopment` , before alpha.1 was
`iris.Config.Render.Template.IsDevelopment`
**Websockets changes**
No need to import the `github.com/kataras/iris/websocket` to use the
`Connection` iteral, the websocket moved inside `kataras/iris` , now all
exported variables' names have the prefix of `Websocket`, so the old
`websocket.Connection` is now `iris.WebsocketConnection`.
Generally, no other changes on the 'frontend API', for response engines
examples and how you can register your own to add more features on
existing response engines or replace them, look
[here](https://github.com/iris-contrib/response).
**BAD SIDE**: E-Book is still pointing on the v3 release, but will be
updated soon.
2016-07-18 16:40:42 +02:00
return ctx . RenderWithStatus ( status , contentXML , v )
2016-06-14 07:45:40 +02:00
}
// MarkdownString parses the (dynamic) markdown string and returns the converted html string
func ( ctx * Context ) MarkdownString ( markdownText string ) string {
2017-02-14 04:54:11 +01:00
out := & bytes . Buffer { }
2017-03-01 14:04:42 +01:00
ok , _ := ctx . framework . policies . RenderPolicy ( out , contentMarkdown , markdownText )
2017-02-14 04:54:11 +01:00
if ok {
return out . String ( )
}
return ""
2016-06-14 07:45:40 +02:00
}
// 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
2017-02-14 04:54:11 +01:00
func ( ctx * Context ) Markdown ( status int , markdown string ) error {
return ctx . HTML ( status , ctx . MarkdownString ( markdown ) )
2016-06-14 07:45:40 +02:00
}
2017-01-02 20:20:17 +01:00
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// --------------------Static content serve by context implementation-------------------
// --------------------Look iris.go for more useful Static web system methods-----------
// -------------------------------------------------------------------------------------
2016-12-12 11:18:59 +01:00
// staticCachePassed checks the IfModifiedSince header and
// returns true if (client-side) duration has expired
func ( ctx * Context ) staticCachePassed ( modtime time . Time ) bool {
if t , err := time . Parse ( ctx . framework . Config . TimeFormat , ctx . RequestHeader ( ifModifiedSince ) ) ; err == nil && modtime . Before ( t . Add ( StaticCacheDuration ) ) {
2017-01-02 20:20:17 +01:00
ctx . ResponseWriter . Header ( ) . Del ( contentType )
ctx . ResponseWriter . Header ( ) . Del ( contentLength )
2016-12-12 11:18:59 +01:00
ctx . SetStatusCode ( StatusNotModified )
return true
}
return false
}
// SetClientCachedBody like SetBody but it sends with an expiration datetime
// which is managed by the client-side (all major browsers supports this feature)
2017-02-14 04:54:11 +01:00
func ( ctx * Context ) SetClientCachedBody ( status int , bodyContent [ ] byte , cType string , modtime time . Time ) error {
2016-12-12 11:18:59 +01:00
if ctx . staticCachePassed ( modtime ) {
2017-02-14 04:54:11 +01:00
return nil
2016-12-12 11:18:59 +01:00
}
modtimeFormatted := modtime . UTC ( ) . Format ( ctx . framework . Config . TimeFormat )
2017-01-02 20:20:17 +01:00
ctx . ResponseWriter . Header ( ) . Set ( contentType , cType )
ctx . ResponseWriter . Header ( ) . Set ( lastModified , modtimeFormatted )
2016-12-12 11:18:59 +01:00
ctx . SetStatusCode ( status )
2017-02-14 04:54:11 +01:00
_ , err := ctx . ResponseWriter . Write ( bodyContent )
return err
2016-12-12 11:18:59 +01:00
}
2016-06-14 07:45:40 +02:00
// ServeContent serves content, headers are autoset
2016-08-17 19:16:23 +02:00
// receives three parameters, it's low-level function, instead you can use .ServeFile(string,bool)/SendFile(string,string)
2016-06-14 07:45:40 +02:00
//
// You can define your own "Content-Type" header also, after this function call
2016-08-17 19:16:23 +02:00
// Doesn't implements resuming (by range), use ctx.SendFile instead
2016-06-14 07:45:40 +02:00
func ( ctx * Context ) ServeContent ( content io . ReadSeeker , filename string , modtime time . Time , gzipCompression bool ) error {
2016-09-09 07:09:03 +02:00
if t , err := time . Parse ( ctx . framework . Config . TimeFormat , ctx . RequestHeader ( ifModifiedSince ) ) ; err == nil && modtime . Before ( t . Add ( 1 * time . Second ) ) {
2017-01-02 20:20:17 +01:00
ctx . ResponseWriter . Header ( ) . Del ( contentType )
ctx . ResponseWriter . Header ( ) . Del ( contentLength )
ctx . SetStatusCode ( StatusNotModified )
2016-06-14 07:45:40 +02:00
return nil
}
2017-03-01 18:17:32 +01:00
ctx . ResponseWriter . Header ( ) . Set ( contentType , typeByExtension ( filename ) )
2017-01-02 20:20:17 +01:00
ctx . ResponseWriter . Header ( ) . Set ( lastModified , modtime . UTC ( ) . Format ( ctx . framework . Config . TimeFormat ) )
ctx . SetStatusCode ( StatusOK )
2016-06-14 07:45:40 +02:00
var out io . Writer
2016-08-14 04:44:36 +02:00
if gzipCompression && ctx . clientAllowsGzip ( ) {
2017-01-02 20:20:17 +01:00
ctx . ResponseWriter . Header ( ) . Add ( varyHeader , acceptEncodingHeader )
2016-08-14 04:44:36 +02:00
ctx . SetHeader ( contentEncodingHeader , "gzip" )
2016-09-09 07:09:03 +02:00
2017-03-01 18:17:32 +01:00
gzipWriter := acquireGzipWriter ( ctx . ResponseWriter )
defer releaseGzipWriter ( gzipWriter )
2016-06-14 07:45:40 +02:00
out = gzipWriter
} else {
2017-01-02 20:20:17 +01:00
out = ctx . ResponseWriter
2016-06-14 07:45:40 +02:00
}
_ , err := io . Copy ( out , content )
2016-09-01 05:34:55 +02:00
return errServeContent . With ( err )
2016-06-14 07:45:40 +02:00
}
// 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
2017-01-02 20:20:17 +01:00
// This function doesn't implement resuming (by range), use ctx.SendFile instead
2016-08-17 19:16:23 +02:00
//
// Use it when you want to serve css/js/... files to the client, for bigger files and 'force-download' use the SendFile
2016-06-14 07:45:40 +02:00
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
//
2016-08-17 19:16:23 +02:00
// Use this instead of ServeFile to 'force-download' bigger files to the client
2017-02-14 04:54:11 +01:00
func ( ctx * Context ) SendFile ( filename string , destinationName string ) error {
2017-01-02 20:20:17 +01:00
ctx . ResponseWriter . Header ( ) . Set ( contentDisposition , "attachment;filename=" + destinationName )
2017-02-14 04:54:11 +01:00
return ctx . ServeFile ( filename , false )
2016-06-14 07:45:40 +02:00
}
2017-01-05 17:59:15 +01:00
// StreamWriter registers the given stream writer for populating
// response body.
//
// Access to context's and/or its' members is forbidden from writer.
//
// This function may be used in the following cases:
//
// * if response body is too big (more than iris.LimitRequestBodySize(if setted)).
// * if response body is streamed from slow external sources.
// * if response body must be streamed to the client in chunks.
// (aka `http server push`).
//
// receives a function which receives the response writer
// and returns false when it should stop writing, otherwise true in order to continue
func ( ctx * Context ) StreamWriter ( writer func ( w io . Writer ) bool ) {
w := ctx . ResponseWriter
notifyClosed := w . CloseNotify ( )
for {
select {
// response writer forced to close, exit.
case <- notifyClosed :
return
default :
shouldContinue := writer ( w )
w . Flush ( )
if ! shouldContinue {
return
}
}
}
}
2017-01-02 20:20:17 +01:00
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// --------------------------------Storage----------------------------------------------
// -----------------------User Values & Path parameters--------------------------------
// -------------------------------------------------------------------------------------
2016-06-14 07:45:40 +02:00
2016-10-25 14:58:18 +02:00
// ValuesLen returns the total length of the user values storage, some of them maybe path parameters
func ( ctx * Context ) ValuesLen ( ) ( n int ) {
2017-01-02 20:20:17 +01:00
return len ( ctx . values )
2016-10-25 14:58:18 +02:00
}
2016-06-14 07:45:40 +02:00
// Get returns the user's value from a key
// if doesn't exists returns nil
func ( ctx * Context ) Get ( key string ) interface { } {
2017-01-02 20:20:17 +01:00
return ctx . values . Get ( key )
2016-06-14 07:45:40 +02:00
}
// GetFmt returns a value which has this format: func(format string, args ...interface{}) string
2017-01-13 04:57:46 +01:00
// if doesn't exists returns a function which returns an empty string.
//
// "translate" is the key of the i18n middlweare
// for more plaese look: https://github.com/iris-contrib/examples/tree/master/middleware_internationalization_i18n
func ( ctx * Context ) getFmt ( key string ) func ( format string , args ... interface { } ) string {
2016-06-14 07:45:40 +02:00
if v , ok := ctx . Get ( key ) . ( func ( format string , args ... interface { } ) string ) ; ok {
return v
}
return func ( format string , args ... interface { } ) string { return "" }
2017-01-13 04:57:46 +01:00
}
// TranslateLanguageContextKey & TranslateFunctionContextKey are used by i18n handlers/middleware
// currently we have only one: https://github.com/iris-contrib/middleware/tree/master/i18n
// but you can use these keys to override the i18n's cookie name (TranslateLanguageContextKey)
// or to store new translate function by using the ctx.Set(iris.TranslateFunctionContextKey, theTrFunc)
var (
TranslateLanguageContextKey = "language"
TranslateFunctionContextKey = "translate"
)
2016-06-14 07:45:40 +02:00
2017-01-13 04:57:46 +01:00
// Translate is the i18n (localization) middleware's function, it just
// calls the ctx.getFmt("translate").
// "translate" is the key of the i18n middlweare
// for more plaese look: https://github.com/iris-contrib/examples/tree/master/middleware_internationalization_i18n
func ( ctx * Context ) Translate ( format string , args ... interface { } ) string {
return ctx . getFmt ( TranslateFunctionContextKey ) ( format , args ... )
2016-06-14 07:45:40 +02:00
}
// 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 ""
}
2016-10-25 14:58:18 +02:00
var errIntParse = errors . New ( "Unable to find or parse the integer, found: %#v" )
// GetInt same as Get but tries to convert the return value as integer
// if nothing found or canno be parsed to integer it returns an error
func ( ctx * Context ) GetInt ( key string ) ( int , error ) {
v := ctx . Get ( key )
if vint , ok := v . ( int ) ; ok {
return vint , nil
} else if vstring , sok := v . ( string ) ; sok {
return strconv . Atoi ( vstring )
2016-06-14 07:45:40 +02:00
}
2016-10-25 14:58:18 +02:00
return - 1 , errIntParse . Format ( v )
2016-06-14 07:45:40 +02:00
}
// Set sets a value to a key in the values map
func ( ctx * Context ) Set ( key string , value interface { } ) {
2017-01-02 20:20:17 +01:00
ctx . values . Set ( key , value )
}
// VisitValues calls visitor for each existing context's temp values.
//
// visitor must not retain references to key and value after returning.
// Make key and/or value copies if you need storing them after returning.
2017-01-10 07:54:39 +01:00
func ( ctx * Context ) VisitValues ( visitor func ( string , interface { } ) ) {
2017-01-02 20:20:17 +01:00
for i , n := 0 , len ( ctx . values ) ; i < n ; i ++ {
kv := & ctx . values [ i ]
2017-01-10 07:54:39 +01:00
visitor ( string ( kv . key ) , kv . value )
2017-01-02 20:20:17 +01:00
}
2016-06-14 07:45:40 +02:00
}
2016-10-25 14:58:18 +02:00
// ParamsLen tries to return all the stored values which values are string, probably most of them will be the path parameters
func ( ctx * Context ) ParamsLen ( ) ( n int ) {
2017-01-10 07:54:39 +01:00
ctx . VisitValues ( func ( kb string , vg interface { } ) {
2016-10-25 14:58:18 +02:00
if _ , ok := vg . ( string ) ; ok {
n ++
}
} )
return
}
// Param returns the string representation of the key's path named parameter's value
// same as GetString
func ( ctx * Context ) Param ( key string ) string {
return ctx . GetString ( key )
}
2017-01-02 20:20:17 +01:00
// ParamDecoded returns a url-query-decoded path parameter's value
func ( ctx * Context ) ParamDecoded ( key string ) string {
return DecodeQuery ( DecodeQuery ( ctx . Param ( key ) ) )
}
2016-10-25 14:58:18 +02:00
// ParamInt returns the int representation of the key's path named parameter's value
// same as GetInt
func ( ctx * Context ) ParamInt ( key string ) ( int , error ) {
return ctx . GetInt ( 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 )
}
// ParamsSentence returns a string implementation of all parameters that this context keeps
// hasthe form of key1=value1,key2=value2...
func ( ctx * Context ) ParamsSentence ( ) string {
var buff bytes . Buffer
2017-02-14 04:54:11 +01:00
ctx . VisitValues ( func ( k string , vi interface { } ) {
v , ok := vi . ( string )
2016-10-25 14:58:18 +02:00
if ! ok {
return
}
buff . WriteString ( k )
buff . WriteString ( "=" )
buff . WriteString ( v )
// we don't know where that (yet) stops so...
buff . WriteString ( "," )
} )
result := buff . String ( )
if len ( result ) < 2 {
return ""
}
return result [ 0 : len ( result ) - 1 ]
}
2017-01-02 20:20:17 +01:00
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// -----------https://github.com/golang/net/blob/master/context/context.go--------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
2016-07-07 00:25:50 +02:00
2017-01-02 20:20:17 +01:00
// Deadline returns the time when work done on behalf of this context
// should be canceled. Deadline returns ok==false when no deadline is
// set. Successive calls to Deadline return the same results.
func ( ctx * Context ) Deadline ( ) ( deadline time . Time , ok bool ) {
2016-06-14 07:45:40 +02:00
return
}
2017-01-02 20:20:17 +01:00
// Done returns a channel that's closed when work done on behalf of this
// context should be canceled. Done may return nil if this context can
// never be canceled. Successive calls to Done return the same value.
//
// WithCancel arranges for Done to be closed when cancel is called;
// WithDeadline arranges for Done to be closed when the deadline
// expires; WithTimeout arranges for Done to be closed when the timeout
// elapses.
//
// Done is provided for use in select statements:
//
// // Stream generates values with DoSomething and sends them to out
// // until DoSomething returns an error or ctx.Done is closed.
// func Stream(ctx context.Context, out chan<- Value) error {
// for {
// v, err := DoSomething(ctx)
// if err != nil {
// return err
// }
// select {
// case <-ctx.Done():
// return ctx.Err()
// case out <- v:
// }
// }
// }
//
// See http://blog.golang.org/pipelines for more examples of how to use
// a Done channel for cancelation.
func ( ctx * Context ) Done ( ) <- chan struct { } {
return nil
2016-06-14 07:45:40 +02:00
}
2017-01-02 20:20:17 +01:00
// Err returns a non-nil error value after Done is closed. Err returns
// Canceled if the context was canceled or DeadlineExceeded if the
// context's deadline passed. No other values for Err are defined.
// After Done is closed, successive calls to Err return the same value.
func ( ctx * Context ) Err ( ) error {
return nil
2016-06-14 07:45:40 +02:00
}
2017-01-02 20:20:17 +01:00
// Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with
// the same key returns the same result.
//
// Use context values only for request-scoped data that transits
// processes and API boundaries, not for passing optional parameters to
// functions.
//
// A key identifies a specific value in a Context. Functions that wish
// to store values in Context typically allocate a key in a global
// variable then use that key as the argument to context.WithValue and
// Context.Value. A key can be any type that supports equality;
// packages should define keys as an unexported type to avoid
// collisions.
//
// Packages that define a Context key should provide type-safe accessors
// for the values stores using that key:
//
// // Package user defines a User type that's stored in Contexts.
// package user
//
// import "golang.org/x/net/context"
//
// // User is the type of value stored in the Contexts.
// type User struct {...}
//
// // key is an unexported type for keys defined in this package.
// // This prevents collisions with keys defined in other packages.
// type key int
//
// // userKey is the key for user.User values in Contexts. It is
// // unexported; clients use user.NewContext and user.FromContext
// // instead of using this key directly.
// var userKey key = 0
//
// // NewContext returns a new Context that carries value u.
// func NewContext(ctx context.Context, u *User) context.Context {
// return context.WithValue(ctx, userKey, u)
// }
//
// // FromContext returns the User value stored in ctx, if any.
// func FromContext(ctx context.Context) (*User, bool) {
// u, ok := ctx.Value(userKey).(*User)
// return u, ok
// }
func ( ctx * Context ) Value ( key interface { } ) interface { } {
if key == 0 {
return ctx . Request
}
if k , ok := key . ( string ) ; ok {
return ctx . GetString ( k )
2016-07-07 00:25:50 +02:00
}
return nil
}
2017-01-02 20:20:17 +01:00
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// --------------------------------Session & Cookies------------------------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
2016-07-07 00:25:50 +02:00
2017-01-02 20:20:17 +01:00
// VisitAllCookies takes a visitor which loops on each (request's) cookie key and value
func ( ctx * Context ) VisitAllCookies ( visitor func ( key string , value string ) ) {
for _ , cookie := range ctx . Request . Cookies ( ) {
visitor ( cookie . Name , cookie . Value )
2016-07-07 00:25:50 +02:00
}
}
2017-01-02 20:20:17 +01:00
// GetCookie returns cookie's value by it's name
// returns empty string if nothing was found
func ( ctx * Context ) GetCookie ( name string ) string {
cookie , err := ctx . Request . Cookie ( name )
if err != nil {
return ""
2016-06-14 07:45:40 +02:00
}
2017-01-02 20:20:17 +01:00
return cookie . Value
2016-06-14 07:45:40 +02:00
}
2017-01-02 20:20:17 +01:00
// SetCookie adds a cookie
func ( ctx * Context ) SetCookie ( cookie * http . Cookie ) {
http . SetCookie ( ctx . ResponseWriter , cookie )
}
2016-08-17 23:28:03 +02:00
2017-01-02 20:20:17 +01:00
// SetCookieKV adds a cookie, receives just a key(string) and a value(string)
//
// Expires on 2 hours by default(unchable)
// use ctx.SetCookie or http.SetCookie instead for more control.
func ( ctx * Context ) SetCookieKV ( name , value string ) {
c := & http . Cookie { }
c . Name = name
c . Value = value
c . HttpOnly = true
c . Expires = time . Now ( ) . Add ( time . Duration ( 120 ) * time . Minute )
ctx . SetCookie ( c )
}
2016-08-16 18:23:12 +02:00
2017-01-02 20:20:17 +01:00
// RemoveCookie deletes a cookie by it's name/key
func ( ctx * Context ) RemoveCookie ( name string ) {
c := & http . Cookie { }
c . Name = name
c . Value = ""
c . Path = "/"
c . HttpOnly = 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...
c . Expires = exp
c . MaxAge = - 1
ctx . SetCookie ( c )
2017-01-10 14:03:02 +01:00
// delete request's cookie also, which is temporary available
2017-01-02 20:20:17 +01:00
ctx . Request . Header . Set ( "Cookie" , "" )
2016-06-14 07:45:40 +02:00
}
2017-02-15 19:06:19 +01:00
var errSessionsPolicyIsMissing = errors . New (
`
manually call of context . Session ( ) for client IP : ' % s ' without specified SessionsPolicy !
Please . Adapt one of the available session managers inside ' kataras / iris / adaptors ' .
Edit your main . go source file to adapt one of these and restart your app .
i . e : lines ( <- -- ) were missing .
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
import (
2017-02-18 06:22:57 +01:00
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter" // or gorillamux
"gopkg.in/kataras/iris.v6/adaptors/sessions" // <--- this line
2017-02-15 19:06:19 +01:00
)
func main ( ) {
app := iris . New ( )
// right below the iris.New()
app . Adapt ( httprouter . New ( ) ) // or gorillamux.New()
mySessions := sessions . New ( sessions . Config {
// Cookie string, the session's client cookie name, for example: "mysessionid"
//
// Defaults to "gosessionid"
Cookie : "mysessionid" ,
// base64 urlencoding,
// if you have strange name cookie name enable this
DecodeCookie : false ,
// it's time.Duration, from the time cookie is created, how long it can be alive?
// 0 means no expire.
Expires : 0 ,
// the length of the sessionid's cookie's value
CookieLength : 32 ,
// if you want to invalid cookies on different subdomains
// of the same host, then enable it
DisableSubdomainPersistence : false ,
} )
// OPTIONALLY:
// import "gopkg.in/kataras/iris.v6/adaptors/sessions/sessiondb/redis"
// or import "github.com/kataras/go-sessions/sessiondb/$any_available_community_database"
// mySessions.UseDatabase(redis.New(...))
app . Adapt ( mySessions ) // <--- and this line were missing.
// the rest of your source code...
// ...
app . Listen ( "%s" )
}
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
` )
// Session returns the current Session.
//
// if SessionsPolicy is missing then a detailed how-to-fix message
// will be visible to the user (DevMode)
// and the return value will be NILL.
func ( ctx * Context ) Session ( ) Session {
policy := ctx . framework . policies . SessionsPolicy
if policy . Start == nil {
ctx . framework . Log ( DevMode ,
errSessionsPolicyIsMissing . Format ( ctx . RemoteAddr ( ) , ctx . framework . Config . VHost ) . Error ( ) )
2016-06-14 07:45:40 +02:00
return nil
}
:rainbow: sessions were re-written, update to 4.0.0-alpha.2, read HISTORY.md
**Sessions were re-written **
- Developers can use more than one 'session database', at the same time,
to store the sessions
- Easy to develop a custom session database (only two functions are
required (Load & Update)), [learn
more](https://github.com/iris-contrib/sessiondb/blob/master/redis/database.go)
- Session databases are located
[here](https://github.com/iris-contrib/sessiondb), contributions are
welcome
- The only frontend deleted 'thing' is the: **config.Sessions.Provider**
- No need to register a database, the sessions works out-of-the-box
- No frontend/API changes except the
`context.Session().Set/Delete/Clear`, they doesn't return errors
anymore, btw they (errors) were always nil :)
- Examples (master branch) were updated.
```sh
$ go get github.com/iris-contrib/sessiondb/$DATABASE
```
```go
db := $DATABASE.New(configurationHere{})
iris.UseSessionDB(db)
```
> Note: Book is not updated yet, examples are up-to-date as always.
2016-07-15 19:50:36 +02:00
if ctx . session == nil {
2017-02-15 19:06:19 +01:00
ctx . session = policy . Start ( ctx . ResponseWriter , ctx . Request )
2016-06-14 07:45:40 +02:00
}
2017-02-15 19:06:19 +01:00
:rainbow: sessions were re-written, update to 4.0.0-alpha.2, read HISTORY.md
**Sessions were re-written **
- Developers can use more than one 'session database', at the same time,
to store the sessions
- Easy to develop a custom session database (only two functions are
required (Load & Update)), [learn
more](https://github.com/iris-contrib/sessiondb/blob/master/redis/database.go)
- Session databases are located
[here](https://github.com/iris-contrib/sessiondb), contributions are
welcome
- The only frontend deleted 'thing' is the: **config.Sessions.Provider**
- No need to register a database, the sessions works out-of-the-box
- No frontend/API changes except the
`context.Session().Set/Delete/Clear`, they doesn't return errors
anymore, btw they (errors) were always nil :)
- Examples (master branch) were updated.
```sh
$ go get github.com/iris-contrib/sessiondb/$DATABASE
```
```go
db := $DATABASE.New(configurationHere{})
iris.UseSessionDB(db)
```
> Note: Book is not updated yet, examples are up-to-date as always.
2016-07-15 19:50:36 +02:00
return ctx . session
2016-06-14 07:45:40 +02:00
}
// SessionDestroy destroys the whole session, calls the provider's destroy and remove the cookie
func ( ctx * Context ) SessionDestroy ( ) {
:rainbow: sessions were re-written, update to 4.0.0-alpha.2, read HISTORY.md
**Sessions were re-written **
- Developers can use more than one 'session database', at the same time,
to store the sessions
- Easy to develop a custom session database (only two functions are
required (Load & Update)), [learn
more](https://github.com/iris-contrib/sessiondb/blob/master/redis/database.go)
- Session databases are located
[here](https://github.com/iris-contrib/sessiondb), contributions are
welcome
- The only frontend deleted 'thing' is the: **config.Sessions.Provider**
- No need to register a database, the sessions works out-of-the-box
- No frontend/API changes except the
`context.Session().Set/Delete/Clear`, they doesn't return errors
anymore, btw they (errors) were always nil :)
- Examples (master branch) were updated.
```sh
$ go get github.com/iris-contrib/sessiondb/$DATABASE
```
```go
db := $DATABASE.New(configurationHere{})
iris.UseSessionDB(db)
```
> Note: Book is not updated yet, examples are up-to-date as always.
2016-07-15 19:50:36 +02:00
if sess := ctx . Session ( ) ; sess != nil {
2017-02-15 19:06:19 +01:00
ctx . framework . policies . SessionsPolicy . Destroy ( ctx . ResponseWriter , ctx . Request )
2016-06-14 07:45:40 +02:00
}
}
2016-10-27 02:17:09 +02:00
var maxAgeExp = regexp . MustCompile ( ` maxage=(\d+) ` )
// MaxAge returns the "cache-control" request header's value
// seconds as int64
// if header not found or parse failed then it returns -1
func ( ctx * Context ) MaxAge ( ) int64 {
header := ctx . RequestHeader ( cacheControl )
if header == "" {
return - 1
}
m := maxAgeExp . FindStringSubmatch ( header )
if len ( m ) == 2 {
if v , err := strconv . Atoi ( m [ 1 ] ) ; err == nil {
return int64 ( v )
}
}
return - 1
}
2017-01-02 20:20:17 +01:00
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
2017-01-04 14:16:53 +01:00
// ---------------------------Transactions & Response Writer Recording------------------
2017-01-02 20:20:17 +01:00
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
2016-12-15 14:14:48 +01:00
2017-01-04 14:16:53 +01:00
// Record transforms the context's basic and direct responseWriter to a ResponseRecorder
// which can be used to reset the body, reset headers, get the body,
// get & set the status code at any time and more
func ( ctx * Context ) Record ( ) {
if w , ok := ctx . ResponseWriter . ( * responseWriter ) ; ok {
ctx . ResponseWriter = acquireResponseRecorder ( w )
}
}
// Recorder returns the context's ResponseRecorder
// if not recording then it starts recording and returns the new context's ResponseRecorder
func ( ctx * Context ) Recorder ( ) * ResponseRecorder {
ctx . Record ( )
return ctx . ResponseWriter . ( * ResponseRecorder )
}
// IsRecording returns the response recorder and a true value
// when the response writer is recording the status code, body, headers and so on,
// else returns nil and false
func ( ctx * Context ) IsRecording ( ) ( * ResponseRecorder , bool ) {
//NOTE:
// two return values in order to minimize the if statement:
// if (Recording) then writer = Recorder()
// instead we do: recorder,ok = Recording()
rr , ok := ctx . ResponseWriter . ( * ResponseRecorder )
return rr , ok
}
2016-12-15 16:16:17 +01:00
// skipTransactionsContextKey set this to any value to stop executing next transactions
// it's a context-key in order to be used from anywhere, set it by calling the SkipTransactions()
2017-01-02 20:20:17 +01:00
const skipTransactionsContextKey = "__IRIS_TRANSACTIONS_SKIP___"
2016-12-15 16:16:17 +01:00
// SkipTransactions if called then skip the rest of the transactions
// or all of them if called before the first transaction
func ( ctx * Context ) SkipTransactions ( ) {
ctx . Set ( skipTransactionsContextKey , 1 )
}
// TransactionsSkipped returns true if the transactions skipped or canceled at all.
func ( ctx * Context ) TransactionsSkipped ( ) bool {
if n , err := ctx . GetInt ( skipTransactionsContextKey ) ; err == nil && n == 1 {
return true
}
return false
}
2016-12-18 13:08:28 +01:00
// non-detailed error log for transacton unexpected panic
var errTransactionInterrupted = errors . New ( "Transaction Interrupted, recovery from panic:\n%s" )
2017-01-02 20:20:17 +01:00
// BeginTransaction starts a scoped transaction.
//
// Can't say a lot here because it will take more than 200 lines to write about.
2017-01-10 14:03:02 +01:00
// You can search third-party articles or books on how Business Transaction works (it's quite simple, especially here).
2017-01-02 20:20:17 +01:00
//
// Note that this is unique and new
// (=I haver never seen any other examples or code in Golang on this subject, so far, as with the most of iris features...)
// it's not covers all paths,
// such as databases, this should be managed by the libraries you use to make your database connection,
// this transaction scope is only for context's response.
2016-12-16 09:20:05 +01:00
// Transactions have their own middleware ecosystem also, look iris.go:UseTransaction.
2016-12-15 14:14:48 +01:00
//
2016-12-16 09:20:05 +01:00
// See https://github.com/iris-contrib/examples/tree/master/transactions for more
2017-01-02 20:20:17 +01:00
func ( ctx * Context ) BeginTransaction ( pipe func ( transaction * Transaction ) ) {
2016-12-18 13:08:28 +01:00
// SILLY NOTE: use of manual pipe type in order of TransactionFunc
// in order to help editors complete the sentence here...
2016-12-15 16:16:17 +01:00
// do NOT begin a transaction when the previous transaction has been failed
2016-12-18 12:24:24 +01:00
// and it was requested scoped or SkipTransactions called manually.
2016-12-15 16:16:17 +01:00
if ctx . TransactionsSkipped ( ) {
return
}
2017-01-04 14:16:53 +01:00
// start recording in order to be able to control the full response writer
ctx . Record ( )
2016-12-18 12:24:24 +01:00
// get a transaction scope from the pool by passing the temp context/
2017-01-02 20:20:17 +01:00
t := newTransaction ( ctx )
2016-12-18 13:08:28 +01:00
defer func ( ) {
if err := recover ( ) ; err != nil {
2017-02-14 04:54:11 +01:00
ctx . Log ( DevMode , errTransactionInterrupted . Format ( err ) . Error ( ) )
2016-12-18 13:08:28 +01:00
// complete (again or not , doesn't matters) the scope without loud
2017-01-02 20:20:17 +01:00
t . Complete ( nil )
2016-12-18 13:08:28 +01:00
// we continue as normal, no need to return here*
}
2016-12-18 12:24:24 +01:00
2017-01-02 20:20:17 +01:00
// write the temp contents to the original writer
t . Context . ResponseWriter . writeTo ( ctx . ResponseWriter )
2017-01-04 14:16:53 +01:00
2017-01-02 20:20:17 +01:00
// give back to the transaction the original writer (SetBeforeFlush works this way and only this way)
// this is tricky but nessecery if we want ctx.EmitError to work inside transactions
t . Context . ResponseWriter = ctx . ResponseWriter
2016-12-18 13:08:28 +01:00
} ( )
2016-12-15 16:16:17 +01:00
2017-01-02 20:20:17 +01:00
// run the worker with its context clone inside.
pipe ( t )
2016-12-15 14:14:48 +01:00
}
2016-06-14 07:45:40 +02:00
// Log logs to the iris defined logger
2017-02-14 04:54:11 +01:00
func ( ctx * Context ) Log ( mode LogMode , format string , a ... interface { } ) {
ctx . framework . Log ( mode , fmt . Sprintf ( format , a ... ) )
2016-06-14 07:45:40 +02:00
}
2016-09-07 06:36:23 +02:00
// Framework returns the Iris instance, containing the configuration and all other fields
func ( ctx * Context ) Framework ( ) * Framework {
return ctx . framework
}