1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664 |
- package context
- import (
- "bytes"
- "encoding/json"
- "encoding/xml"
- "fmt"
- "io"
- "io/ioutil"
- "mime"
- "mime/multipart"
- "net"
- "net/http"
- "net/url"
- "os"
- "path"
- "path/filepath"
- "regexp"
- "strconv"
- "strings"
- "sync/atomic"
- "time"
- "github.com/kataras/iris/core/errors"
- "github.com/kataras/iris/core/memstore"
- "github.com/Shopify/goreferrer"
- "github.com/fatih/structs"
- formbinder "github.com/iris-contrib/formBinder"
- "github.com/json-iterator/go"
- "github.com/microcosm-cc/bluemonday"
- "gopkg.in/russross/blackfriday.v2"
- "gopkg.in/yaml.v2"
- )
- type (
- // 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.
- //
- // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-custom-per-type/main.go
- BodyDecoder interface {
- Decode(data []byte) error
- }
- // Unmarshaler is the interface implemented by types that can unmarshal any raw data.
- // TIP INFO: Any pointer to a value which implements the BodyDecoder can be override the unmarshaler.
- Unmarshaler interface {
- Unmarshal(data []byte, outPtr interface{}) error
- }
- // UnmarshalerFunc a shortcut for the Unmarshaler interface
- //
- // See 'Unmarshaler' and 'BodyDecoder' for more.
- //
- // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-custom-via-unmarshaler/main.go
- UnmarshalerFunc func(data []byte, outPtr interface{}) error
- )
- // 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)
- }
- // RequestParams is a key string - value string storage which
- // context's request dynamic path params are being kept.
- // Empty if the route is static.
- type RequestParams struct {
- store memstore.Store
- }
- // Set adds a key-value pair to the path parameters values
- // it's being called internally so it shouldn't be used as a local storage by the user, use `ctx.Values()` instead.
- func (r *RequestParams) Set(key, value string) {
- r.store.Set(key, value)
- }
- // Visit accepts a visitor which will be filled
- // by the key-value params.
- func (r *RequestParams) Visit(visitor func(key string, value string)) {
- r.store.Visit(func(k string, v interface{}) {
- visitor(k, v.(string)) // always string here.
- })
- }
- var emptyEntry memstore.Entry
- // GetEntryAt returns the internal Entry of the memstore based on its index,
- // the stored index by the router.
- // If not found then it returns a zero Entry and false.
- func (r RequestParams) GetEntryAt(index int) (memstore.Entry, bool) {
- if len(r.store) > index {
- return r.store[index], true
- }
- return emptyEntry, false
- }
- // GetEntry returns the internal Entry of the memstore based on its "key".
- // If not found then it returns a zero Entry and false.
- func (r RequestParams) GetEntry(key string) (memstore.Entry, bool) {
- // we don't return the pointer here, we don't want to give the end-developer
- // the strength to change the entry that way.
- if e := r.store.GetEntry(key); e != nil {
- return *e, true
- }
- return emptyEntry, false
- }
- // Get returns a path parameter's value based on its route's dynamic path key.
- func (r RequestParams) Get(key string) string {
- return r.store.GetString(key)
- }
- // GetTrim returns a path parameter's value without trailing spaces based on its route's dynamic path key.
- func (r RequestParams) GetTrim(key string) string {
- return strings.TrimSpace(r.Get(key))
- }
- // GetEscape returns a path parameter's double-url-query-escaped value based on its route's dynamic path key.
- func (r RequestParams) GetEscape(key string) string {
- return DecodeQuery(DecodeQuery(r.Get(key)))
- }
- // GetDecoded returns a path parameter's double-url-query-escaped value based on its route's dynamic path key.
- // same as `GetEscape`.
- func (r RequestParams) GetDecoded(key string) string {
- return r.GetEscape(key)
- }
- // GetInt returns the path parameter's value as int, based on its key.
- // It checks for all available types of int, including int64, strings etc.
- // It will return -1 and a non-nil error if parameter wasn't found.
- func (r RequestParams) GetInt(key string) (int, error) {
- return r.store.GetInt(key)
- }
- // GetInt64 returns the path paramete's value as int64, based on its key.
- // It checks for all available types of int, including int, strings etc.
- // It will return -1 and a non-nil error if parameter wasn't found.
- func (r RequestParams) GetInt64(key string) (int64, error) {
- return r.store.GetInt64(key)
- }
- // GetFloat64 returns a path parameter's value based as float64 on its route's dynamic path key.
- // It checks for all available types of int, including float64, int, strings etc.
- // It will return -1 and a non-nil error if parameter wasn't found.
- func (r RequestParams) GetFloat64(key string) (float64, error) {
- return r.store.GetFloat64(key)
- }
- // GetUint64 returns the path paramete's value as uint64, based on its key.
- // It checks for all available types of int, including int, uint64, int64, strings etc.
- // It will return 0 and a non-nil error if parameter wasn't found.
- func (r RequestParams) GetUint64(key string) (uint64, error) {
- return r.store.GetUint64(key)
- }
- // GetBool returns the path parameter's value as bool, based on its key.
- // a string which is "1" or "t" or "T" or "TRUE" or "true" or "True"
- // or "0" or "f" or "F" or "FALSE" or "false" or "False".
- // Any other value returns an error.
- func (r RequestParams) GetBool(key string) (bool, error) {
- return r.store.GetBool(key)
- }
- // GetIntUnslashed same as Get but it removes the first slash if found.
- // Usage: Get an id from a wildcard path.
- //
- // Returns -1 with an error if the parameter couldn't be found.
- func (r RequestParams) GetIntUnslashed(key string) (int, error) {
- v := r.Get(key)
- if v != "" {
- if len(v) > 1 {
- if v[0] == '/' {
- v = v[1:]
- }
- }
- return strconv.Atoi(v)
- }
- return -1, fmt.Errorf("unable to find int for '%s'", key)
- }
- // Len returns the full length of the parameters.
- func (r RequestParams) Len() int {
- return r.store.Len()
- }
- // Context is the midle-man server's "object" for the clients.
- //
- // A New context is being acquired from a sync.Pool on each connection.
- // The Context is the most important thing on the iris's http flow.
- //
- // Developers send responses to the client's request through a Context.
- // Developers get request information from the client's request a Context.
- //
- // This context is an implementation of the context.Context sub-package.
- // context.Context is very extensible and developers can override
- // its methods if that is actually needed.
- type Context interface {
- // BeginRequest is executing once for each request
- // it should prepare the (new or acquired from pool) context's fields for the new request.
- //
- // To follow the iris' flow, developer should:
- // 1. reset handlers to nil
- // 2. reset values to empty
- // 3. reset sessions to nil
- // 4. reset response writer to the http.ResponseWriter
- // 5. reset request to the *http.Request
- // and any other optional steps, depends on dev's application type.
- BeginRequest(http.ResponseWriter, *http.Request)
- // EndRequest is executing once after a response to the request was sent and this context is useless or released.
- //
- // To follow the iris' flow, developer should:
- // 1. flush the response writer's result
- // 2. release the response writer
- // and any other optional steps, depends on dev's application type.
- EndRequest()
- // ResponseWriter returns an http.ResponseWriter compatible response writer, as expected.
- ResponseWriter() ResponseWriter
- // ResetResponseWriter should change or upgrade the Context's ResponseWriter.
- ResetResponseWriter(ResponseWriter)
- // Request returns the original *http.Request, as expected.
- Request() *http.Request
- // SetCurrentRouteName sets the route's name internally,
- // in order to be able to find the correct current "read-only" Route when
- // end-developer calls the `GetCurrentRoute()` function.
- // It's being initialized by the Router, if you change that name
- // manually nothing really happens except that you'll get other
- // route via `GetCurrentRoute()`.
- // Instead, to execute a different path
- // from this context you should use the `Exec` function
- // or change the handlers via `SetHandlers/AddHandler` functions.
- SetCurrentRouteName(currentRouteName string)
- // GetCurrentRoute returns the current registered "read-only" route that
- // was being registered to this request's path.
- GetCurrentRoute() RouteReadOnly
- // Do calls the SetHandlers(handlers)
- // and executes the first handler,
- // handlers should not be empty.
- //
- // It's used by the router, developers may use that
- // to replace and execute handlers immediately.
- Do(Handlers)
- // AddHandler can add handler(s)
- // to the current request in serve-time,
- // these handlers are not persistenced to the router.
- //
- // Router is calling this function to add the route's handler.
- // If AddHandler called then the handlers will be inserted
- // to the end of the already-defined route's handler.
- //
- AddHandler(...Handler)
- // SetHandlers replaces all handlers with the new.
- SetHandlers(Handlers)
- // Handlers keeps tracking of the current handlers.
- Handlers() Handlers
- // HandlerIndex sets the current index of the
- // current context's handlers chain.
- // If -1 passed then it just returns the
- // current handler index without change the current index.rns that index, useless return value.
- //
- // Look Handlers(), Next() and StopExecution() too.
- HandlerIndex(n int) (currentIndex int)
- // Proceed is an alternative way to check if a particular handler
- // has been executed and called the `ctx.Next` function inside it.
- // This is useful only when you run a handler inside
- // another handler. It justs checks for before index and the after index.
- //
- // A usecase example is when you want to execute a middleware
- // inside controller's `BeginRequest` that calls the `ctx.Next` inside it.
- // The Controller looks the whole flow (BeginRequest, method handler, EndRequest)
- // as one handler, so `ctx.Next` will not be reflected to the method handler
- // if called from the `BeginRequest`.
- //
- // Although `BeginRequest` should NOT be used to call other handlers,
- // the `BeginRequest` has been introduced to be able to set
- // common data to all method handlers before their execution.
- // Controllers can accept middleware(s) from the MVC's Application's Router as normally.
- //
- // That said let's see an example of `ctx.Proceed`:
- //
- // var authMiddleware = basicauth.New(basicauth.Config{
- // Users: map[string]string{
- // "admin": "password",
- // },
- // })
- //
- // func (c *UsersController) BeginRequest(ctx iris.Context) {
- // if !ctx.Proceed(authMiddleware) {
- // ctx.StopExecution()
- // }
- // }
- // This Get() will be executed in the same handler as `BeginRequest`,
- // internally controller checks for `ctx.StopExecution`.
- // So it will not be fired if BeginRequest called the `StopExecution`.
- // func(c *UsersController) Get() []models.User {
- // return c.Service.GetAll()
- //}
- // Alternative way is `!ctx.IsStopped()` if middleware make use of the `ctx.StopExecution()` on failure.
- Proceed(Handler) bool
- // HandlerName returns the current handler's name, helpful for debugging.
- HandlerName() string
- // Next calls all the next handler from the handlers chain,
- // it should be used inside a middleware.
- //
- // Note: Custom context should override this method in order to be able to pass its own context.Context implementation.
- Next()
- // NextOr checks if chain has a next handler, if so then it executes it
- // otherwise it sets a new chain assigned to this Context based on the given handler(s)
- // and executes its first handler.
- //
- // Returns true if next handler exists and executed, otherwise false.
- //
- // Note that if no next handler found and handlers are missing then
- // it sends a Status Not Found (404) to the client and it stops the execution.
- NextOr(handlers ...Handler) bool
- // NextOrNotFound checks if chain has a next handler, if so then it executes it
- // otherwise it sends a Status Not Found (404) to the client and stops the execution.
- //
- // Returns true if next handler exists and executed, otherwise false.
- NextOrNotFound() bool
- // NextHandler returns (it doesn't execute) the next handler from the handlers chain.
- //
- // Use .Skip() to skip this handler if needed to execute the next of this returning handler.
- NextHandler() Handler
- // Skip skips/ignores the next handler from the handlers chain,
- // it should be used inside a middleware.
- Skip()
- // StopExecution if called then the following .Next calls are ignored,
- // as a result the next handlers in the chain will not be fire.
- StopExecution()
- // IsStopped checks and returns true if the current position of the Context is 255,
- // means that the StopExecution() was called.
- IsStopped() bool
- // OnConnectionClose registers the "cb" function which will fire (on its own goroutine, no need to be registered goroutine by the end-dev)
- // when the underlying connection has gone away.
- //
- // This mechanism can be used to cancel long operations on the server
- // if the client has disconnected before the response is ready.
- //
- // It depends on the `http#CloseNotify`.
- // CloseNotify may wait to notify until Request.Body has been
- // fully read.
- //
- // After the main Handler has returned, there is no guarantee
- // that the channel receives a value.
- //
- // Finally, it reports whether the protocol supports pipelines (HTTP/1.1 with pipelines disabled is not supported).
- // The "cb" will not fire for sure if the output value is false.
- //
- // Note that you can register only one callback for the entire request handler chain/per route.
- //
- // Look the `ResponseWriter#CloseNotifier` for more.
- OnConnectionClose(fnGoroutine func()) bool
- // OnClose registers the callback function "cb" to the underline connection closing event using the `Context#OnConnectionClose`
- // and also in the end of the request handler using the `ResponseWriter#SetBeforeFlush`.
- // Note that you can register only one callback for the entire request handler chain/per route.
- //
- // Look the `Context#OnConnectionClose` and `ResponseWriter#SetBeforeFlush` for more.
- OnClose(cb func())
- // +------------------------------------------------------------+
- // | Current "user/request" storage |
- // | and share information between the handlers - Values(). |
- // | Save and get named path parameters - Params() |
- // +------------------------------------------------------------+
- // Params returns the current url's named parameters key-value storage.
- // Named path parameters are being saved here.
- // This storage, as the whole Context, is per-request lifetime.
- Params() *RequestParams
- // Values returns the current "user" storage.
- // Named path parameters and any optional data can be saved here.
- // This storage, as the whole Context, is per-request lifetime.
- //
- // You can use this function to Set and Get local values
- // that can be used to share information between handlers and middleware.
- Values() *memstore.Store
- // Translate is the i18n (localization) middleware's function,
- // it calls the Get("translate") to return the translated value.
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/miscellaneous/i18n
- Translate(format string, args ...interface{}) string
- // +------------------------------------------------------------+
- // | Path, Host, Subdomain, IP, Headers etc... |
- // +------------------------------------------------------------+
- // Method returns the request.Method, the client's http method to the server.
- Method() string
- // Path returns the full request path,
- // escaped if EnablePathEscape config field is true.
- Path() string
- // RequestPath returns the full request path,
- // based on the 'escape'.
- RequestPath(escape bool) string
- // Host returns the host part of the current url.
- Host() string
- // Subdomain returns the subdomain of this request, if any.
- // Note that this is a fast method which does not cover all cases.
- Subdomain() (subdomain string)
- // IsWWW returns true if the current subdomain (if any) is www.
- IsWWW() bool
- // RemoteAddr tries to parse and return the real client's request IP.
- //
- // Based on allowed headers names that can be modified from Configuration.RemoteAddrHeaders.
- //
- // If parse based on these headers fail then it will return the Request's `RemoteAddr` field
- // which is filled by the server before the HTTP handler.
- //
- // Look `Configuration.RemoteAddrHeaders`,
- // `Configuration.WithRemoteAddrHeader(...)`,
- // `Configuration.WithoutRemoteAddrHeader(...)` for more.
- RemoteAddr() string
- // GetHeader returns the request header's value based on its name.
- GetHeader(name string) string
- // IsAjax returns true if this request is an 'ajax request'( XMLHttpRequest)
- //
- // There is no a 100% way of knowing that a request was made via Ajax.
- // You should never trust data coming from the client, they can be easily overcome by spoofing.
- //
- // Note that "X-Requested-With" Header can be modified by any client(because of "X-"),
- // so don't rely on IsAjax for really serious stuff,
- // try to find another way of detecting the type(i.e, content type),
- // there are many blogs that describe these problems and provide different kind of solutions,
- // it's always depending on the application you're building,
- // this is the reason why this `IsAjax`` is simple enough for general purpose use.
- //
- // Read more at: https://developer.mozilla.org/en-US/docs/AJAX
- // and https://xhr.spec.whatwg.org/
- IsAjax() bool
- // IsMobile checks if client is using a mobile device(phone or tablet) to communicate with this server.
- // If the return value is true that means that the http client using a mobile
- // device to communicate with the server, otherwise false.
- //
- // Keep note that this checks the "User-Agent" request header.
- IsMobile() bool
- // GetReferrer extracts and returns the information from the "Referer" header as specified
- // in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
- // or by the URL query parameter "referer".
- GetReferrer() Referrer
- // +------------------------------------------------------------+
- // | Headers helpers |
- // +------------------------------------------------------------+
- // Header adds a header to the response writer.
- Header(name string, value string)
- // ContentType sets the response writer's header key "Content-Type" to the 'cType'.
- ContentType(cType string)
- // GetContentType returns the response writer's header value of "Content-Type"
- // which may, setted before with the 'ContentType'.
- GetContentType() string
- // GetContentType returns the request's header value of "Content-Type".
- GetContentTypeRequested() string
- // GetContentLength returns the request's header value of "Content-Length".
- // Returns 0 if header was unable to be found or its value was not a valid number.
- GetContentLength() int64
- // StatusCode sets the status code header to the response.
- // Look .`GetStatusCode` too.
- StatusCode(statusCode int)
- // GetStatusCode returns the current status code of the response.
- // Look `StatusCode` too.
- GetStatusCode() int
- // Redirect sends a redirect response to the client
- // to a specific url or relative path.
- // accepts 2 parameters string and an optional int
- // first parameter is the url to redirect
- // second parameter is the http status should send,
- // default is 302 (StatusFound),
- // you can set it to 301 (Permant redirect)
- // or 303 (StatusSeeOther) if POST method,
- // or StatusTemporaryRedirect(307) if that's nessecery.
- Redirect(urlToRedirect string, statusHeader ...int)
- // +------------------------------------------------------------+
- // | Various Request and Post Data |
- // +------------------------------------------------------------+
- // URLParam returns true if the url parameter exists, otherwise false.
- URLParamExists(name string) bool
- // URLParamDefault returns the get parameter from a request,
- // if not found then "def" is returned.
- URLParamDefault(name string, def string) string
- // URLParam returns the get parameter from a request, if any.
- URLParam(name string) string
- // URLParamTrim returns the url query parameter with trailing white spaces removed from a request.
- URLParamTrim(name string) string
- // URLParamTrim returns the escaped url query parameter from a request.
- URLParamEscape(name string) string
- // URLParamInt returns the url query parameter as int value from a request,
- // returns -1 and an error if parse failed.
- URLParamInt(name string) (int, error)
- // URLParamIntDefault returns the url query parameter as int value from a request,
- // if not found or parse failed then "def" is returned.
- URLParamIntDefault(name string, def int) int
- // URLParamInt32Default returns the url query parameter as int32 value from a request,
- // if not found or parse failed then "def" is returned.
- URLParamInt32Default(name string, def int32) int32
- // URLParamInt64 returns the url query parameter as int64 value from a request,
- // returns -1 and an error if parse failed.
- URLParamInt64(name string) (int64, error)
- // URLParamInt64Default returns the url query parameter as int64 value from a request,
- // if not found or parse failed then "def" is returned.
- URLParamInt64Default(name string, def int64) int64
- // URLParamFloat64 returns the url query parameter as float64 value from a request,
- // returns -1 and an error if parse failed.
- URLParamFloat64(name string) (float64, error)
- // URLParamFloat64Default returns the url query parameter as float64 value from a request,
- // if not found or parse failed then "def" is returned.
- URLParamFloat64Default(name string, def float64) float64
- // URLParamBool returns the url query parameter as boolean value from a request,
- // returns an error if parse failed or not found.
- URLParamBool(name string) (bool, error)
- // URLParams returns a map of GET query parameters separated by comma if more than one
- // it returns an empty map if nothing found.
- URLParams() map[string]string
- // FormValueDefault returns a single parsed form value by its "name",
- // including both the URL field's query parameters and the POST or PUT form data.
- //
- // Returns the "def" if not found.
- FormValueDefault(name string, def string) string
- // FormValue returns a single parsed form value by its "name",
- // including both the URL field's query parameters and the POST or PUT form data.
- FormValue(name string) string
- // FormValues returns the parsed form data, including both the URL
- // field's query parameters and the POST or PUT form data.
- //
- // The default form's memory maximum size is 32MB, it can be changed by the
- // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
- //
- // NOTE: A check for nil is necessary.
- FormValues() map[string][]string
- // PostValueDefault returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name".
- //
- // If not found then "def" is returned instead.
- PostValueDefault(name string, def string) string
- // PostValue returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name"
- PostValue(name string) string
- // PostValueTrim returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", without trailing spaces.
- PostValueTrim(name string) string
- // PostValueInt returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", as int.
- //
- // If not found returns -1 and a non-nil error.
- PostValueInt(name string) (int, error)
- // PostValueIntDefault returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", as int.
- //
- // If not found returns or parse errors the "def".
- PostValueIntDefault(name string, def int) int
- // PostValueInt64 returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", as float64.
- //
- // If not found returns -1 and a no-nil error.
- PostValueInt64(name string) (int64, error)
- // PostValueInt64Default returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", as int64.
- //
- // If not found or parse errors returns the "def".
- PostValueInt64Default(name string, def int64) int64
- // PostValueInt64Default returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", as float64.
- //
- // If not found returns -1 and a non-nil error.
- PostValueFloat64(name string) (float64, error)
- // PostValueInt64Default returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", as float64.
- //
- // If not found or parse errors returns the "def".
- PostValueFloat64Default(name string, def float64) float64
- // PostValueInt64Default returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", as bool.
- //
- // If not found or value is false, then it returns false, otherwise true.
- PostValueBool(name string) (bool, error)
- // PostValues returns all the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name" as a string slice.
- //
- // The default form's memory maximum size is 32MB, it can be changed by the
- // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
- PostValues(name string) []string
- // FormFile returns the first uploaded file that received from the client.
- //
- // The default form's memory maximum size is 32MB, it can be changed by the
- // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/http_request/upload-file
- FormFile(key string) (multipart.File, *multipart.FileHeader, error)
- // UploadFormFiles uploads any received file(s) from the client
- // to the system physical location "destDirectory".
- //
- // The second optional argument "before" gives caller the chance to
- // modify the *miltipart.FileHeader before saving to the disk,
- // it can be used to change a file's name based on the current request,
- // all FileHeader's options can be changed. You can ignore it if
- // you don't need to use this capability before saving a file to the disk.
- //
- // Note that it doesn't check if request body streamed.
- //
- // Returns the copied length as int64 and
- // a not nil error if at least one new file
- // can't be created due to the operating system's permissions or
- // http.ErrMissingFile if no file received.
- //
- // If you want to receive & accept files and manage them manually you can use the `context#FormFile`
- // instead and create a copy function that suits your needs, the below is for generic usage.
- //
- // The default form's memory maximum size is 32MB, it can be changed by the
- // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
- //
- // See `FormFile` to a more controlled to receive a file.
- //
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/http_request/upload-files
- UploadFormFiles(destDirectory string, before ...func(Context, *multipart.FileHeader)) (n int64, err error)
- // +------------------------------------------------------------+
- // | Custom HTTP Errors |
- // +------------------------------------------------------------+
- // NotFound emits an error 404 to the client, using the specific custom error error handler.
- // Note that you may need to call ctx.StopExecution() if you don't want the next handlers
- // to be executed. Next handlers are being executed on iris because you can alt the
- // error code and change it to a more specific one, i.e
- // users := app.Party("/users")
- // users.Done(func(ctx context.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }})
- NotFound()
- // +------------------------------------------------------------+
- // | Body Readers |
- // +------------------------------------------------------------+
- // SetMaxRequestBodySize sets a limit to the request body size
- // should be called before reading the request body from the client.
- SetMaxRequestBodySize(limitOverBytes int64)
- // UnmarshalBody reads the request's body and binds it to a value or pointer of any type.
- // Examples of usage: context.ReadJSON, context.ReadXML.
- //
- // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-custom-via-unmarshaler/main.go
- //
- // UnmarshalBody does not check about gzipped data.
- // Do not rely on compressed data incoming to your server. The main reason is: https://en.wikipedia.org/wiki/Zip_bomb
- // However you are still free to read the `ctx.Request().Body io.Reader` manually.
- UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) error
- // ReadJSON reads JSON from request's body and binds it to a pointer of a value of any json-valid type.
- //
- // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-json/main.go
- ReadJSON(jsonObjectPtr interface{}) error
- // ReadXML reads XML from request's body and binds it to a pointer of a value of any xml-valid type.
- //
- // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go
- ReadXML(xmlObjectPtr interface{}) error
- // ReadForm binds the formObject with the form data
- // it supports any kind of struct.
- //
- // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-form/main.go
- ReadForm(formObjectPtr interface{}) error
- // +------------------------------------------------------------+
- // | Body (raw) Writers |
- // +------------------------------------------------------------+
- // Write writes the data to the connection as part of an HTTP reply.
- //
- // If WriteHeader has not yet been called, Write calls
- // WriteHeader(http.StatusOK) before writing the data. If the Header
- // does not contain a Content-Type line, Write adds a Content-Type set
- // to the result of passing the initial 512 bytes of written data to
- // DetectContentType.
- //
- // Depending on the HTTP protocol version and the client, calling
- // Write or WriteHeader may prevent future reads on the
- // Request.Body. For HTTP/1.x requests, handlers should read any
- // needed request body data before writing the response. Once the
- // headers have been flushed (due to either an explicit Flusher.Flush
- // call or writing enough data to trigger a flush), the request body
- // may be unavailable. For HTTP/2 requests, the Go HTTP server permits
- // handlers to continue to read the request body while concurrently
- // writing the response. However, such behavior may not be supported
- // by all HTTP/2 clients. Handlers should read before writing if
- // possible to maximize compatibility.
- Write(body []byte) (int, error)
- // Writef formats according to a format specifier and writes to the response.
- //
- // Returns the number of bytes written and any write error encountered.
- Writef(format string, args ...interface{}) (int, error)
- // WriteString writes a simple string to the response.
- //
- // Returns the number of bytes written and any write error encountered.
- WriteString(body string) (int, error)
- // SetLastModified sets the "Last-Modified" based on the "modtime" input.
- // If "modtime" is zero then it does nothing.
- //
- // It's mostly internally on core/router and context packages.
- //
- // Note that modtime.UTC() is being used instead of just modtime, so
- // you don't have to know the internals in order to make that works.
- SetLastModified(modtime time.Time)
- // CheckIfModifiedSince checks if the response is modified since the "modtime".
- // Note that it has nothing to do with server-side caching.
- // It does those checks by checking if the "If-Modified-Since" request header
- // sent by client or a previous server response header
- // (e.g with WriteWithExpiration or StaticEmbedded or Favicon etc.)
- // is a valid one and it's before the "modtime".
- //
- // A check for !modtime && err == nil is necessary to make sure that
- // it's not modified since, because it may return false but without even
- // had the chance to check the client-side (request) header due to some errors,
- // like the HTTP Method is not "GET" or "HEAD" or if the "modtime" is zero
- // or if parsing time from the header failed.
- //
- // It's mostly used internally, e.g. `context#WriteWithExpiration`.
- //
- // Note that modtime.UTC() is being used instead of just modtime, so
- // you don't have to know the internals in order to make that works.
- CheckIfModifiedSince(modtime time.Time) (bool, error)
- // WriteNotModified sends a 304 "Not Modified" status code to the client,
- // it makes sure that the content type, the content length headers
- // and any "ETag" are removed before the response sent.
- //
- // It's mostly used internally on core/router/fs.go and context methods.
- WriteNotModified()
- // WriteWithExpiration like Write but it sends with an expiration datetime
- // which is refreshed every package-level `StaticCacheDuration` field.
- WriteWithExpiration(body []byte, modtime time.Time) (int, error)
- // 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
- StreamWriter(writer func(w io.Writer) bool)
- // +------------------------------------------------------------+
- // | Body Writers with compression |
- // +------------------------------------------------------------+
- // ClientSupportsGzip retruns true if the client supports gzip compression.
- ClientSupportsGzip() bool
- // 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)
- // You may re-use this function in the same handler
- // to write more data many times without any troubles.
- WriteGzip(b []byte) (int, error)
- // 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.
- TryWriteGzip(b []byte) (int, error)
- // GzipResponseWriter converts the current response writer into a response writer
- // which when its .Write called it compress the data to gzip and writes them to the client.
- //
- // Can be also disabled with its .Disable and .ResetBody to rollback to the usual response writer.
- GzipResponseWriter() *GzipResponseWriter
- // Gzip enables or disables (if enabled before) the gzip response writer,if the client
- // supports gzip compression, so the following response data will
- // be sent as compressed gzip data to the client.
- Gzip(enable bool)
- // +------------------------------------------------------------+
- // | Rich Body Content Writers/Renderers |
- // +------------------------------------------------------------+
- // ViewLayout sets the "layout" option if and when .View
- // is being called afterwards, in the same request.
- // Useful when need to set or/and change a layout based on the previous handlers in the chain.
- //
- // Note that the 'layoutTmplFile' argument can be setted to iris.NoLayout || view.NoLayout
- // to disable the layout for a specific view render action,
- // it disables the engine's configuration's layout property.
- //
- // Look .ViewData and .View too.
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/view/context-view-data/
- ViewLayout(layoutTmplFile string)
- // ViewData saves one or more key-value pair in order to be passed if and when .View
- // is being called afterwards, in the same request.
- // Useful when need to set or/and change template data from previous hanadlers in the chain.
- //
- // If .View's "binding" argument is not nil and it's not a type of map
- // then these data are being ignored, binding has the priority, so the main route's handler can still decide.
- // If binding is a map or context.Map then these data are being added to the view data
- // and passed to the template.
- //
- // After .View, the data are not destroyed, in order to be re-used if needed (again, in the same request as everything else),
- // to clear the view data, developers can call:
- // ctx.Set(ctx.Application().ConfigurationReadOnly().GetViewDataContextKey(), nil)
- //
- // If 'key' is empty then the value is added as it's (struct or map) and developer is unable to add other value.
- //
- // Look .ViewLayout and .View too.
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/view/context-view-data/
- ViewData(key string, value interface{})
- // GetViewData returns the values registered by `context#ViewData`.
- // The return value is `map[string]interface{}`, this means that
- // if a custom struct registered to ViewData then this function
- // will try to parse it to map, if failed then the return value is nil
- // A check for nil is always a good practise if different
- // kind of values or no data are registered via `ViewData`.
- //
- // Similarly to `viewData := ctx.Values().Get("iris.viewData")` or
- // `viewData := ctx.Values().Get(ctx.Application().ConfigurationReadOnly().GetViewDataContextKey())`.
- GetViewData() map[string]interface{}
- // View renders a template based on the registered view engine(s).
- // First argument accepts the filename, relative to the view engine's Directory and Extension,
- // i.e: if directory is "./templates" and want to render the "./templates/users/index.html"
- // then you pass the "users/index.html" as the filename argument.
- //
- // The second optional argument can receive a single "view model"
- // that will be binded to the view template if it's not nil,
- // otherwise it will check for previous view data stored by the `ViewData`
- // even if stored at any previous handler(middleware) for the same request.
- //
- // Look .ViewData` and .ViewLayout too.
- //
- // Examples: https://github.com/kataras/iris/tree/master/_examples/view
- View(filename string, optionalViewModel ...interface{}) error
- // Binary writes out the raw bytes as binary data.
- Binary(data []byte) (int, error)
- // Text writes out a string as plain text.
- Text(text string) (int, error)
- // HTML writes out a string as text/html.
- HTML(htmlContents string) (int, error)
- // JSON marshals the given interface object and writes the JSON response.
- JSON(v interface{}, options ...JSON) (int, error)
- // JSONP marshals the given interface object and writes the JSON response.
- JSONP(v interface{}, options ...JSONP) (int, error)
- // XML marshals the given interface object and writes the XML response.
- XML(v interface{}, options ...XML) (int, error)
- // Markdown parses the markdown to html and renders its result to the client.
- Markdown(markdownB []byte, options ...Markdown) (int, error)
- // YAML parses the "v" using the yaml parser and renders its result to the client.
- YAML(v interface{}) (int, error)
- // +------------------------------------------------------------+
- // | Serve files |
- // +------------------------------------------------------------+
- // ServeContent serves content, headers are autoset
- // receives three parameters, it's low-level function, instead you can use .ServeFile(string,bool)/SendFile(string,string)
- //
- //
- // You can define your own "Content-Type" with `context#ContentType`, before this function call.
- //
- // This function doesn't support resuming (by range),
- // use ctx.SendFile or router's `StaticWeb` instead.
- ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error
- // ServeFile serves a file (to send a file, a zip for example to the client you should use the `SendFile` instead)
- // receives two parameters
- // filename/path (string)
- // gzipCompression (bool)
- //
- // You can define your own "Content-Type" with `context#ContentType`, before this function call.
- //
- // This function doesn't support resuming (by range),
- // use ctx.SendFile or router's `StaticWeb` instead.
- //
- // Use it when you want to serve dynamic files to the client.
- ServeFile(filename string, gzipCompression bool) error
- // SendFile sends file for force-download to the client
- //
- // Use this instead of ServeFile to 'force-download' bigger files to the client.
- SendFile(filename string, destinationName string) error
- // +------------------------------------------------------------+
- // | Cookies |
- // +------------------------------------------------------------+
- // SetCookie adds a cookie.
- // Use of the "options" is not required, they can be used to amend the "cookie".
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
- SetCookie(cookie *http.Cookie, options ...CookieOption)
- // SetCookieKV adds a cookie, requires the name(string) and the value(string).
- //
- // By default it expires at 2 hours and it's added to the root path,
- // use the `CookieExpires` and `CookiePath` to modify them.
- // Alternatively: ctx.SetCookie(&http.Cookie{...})
- //
- // If you want to set custom the path:
- // ctx.SetCookieKV(name, value, iris.CookiePath("/custom/path/cookie/will/be/stored"))
- //
- // If you want to be visible only to current request path:
- // ctx.SetCookieKV(name, value, iris.CookieCleanPath/iris.CookiePath(""))
- // More:
- // iris.CookieExpires(time.Duration)
- // iris.CookieHTTPOnly(false)
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
- SetCookieKV(name, value string, options ...CookieOption)
- // GetCookie returns cookie's value by it's name
- // returns empty string if nothing was found.
- //
- // If you want more than the value then:
- // cookie, err := ctx.Request().Cookie("name")
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
- GetCookie(name string, options ...CookieOption) string
- // RemoveCookie deletes a cookie by it's name and path = "/".
- // Tip: change the cookie's path to the current one by: RemoveCookie("name", iris.CookieCleanPath)
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
- RemoveCookie(name string, options ...CookieOption)
- // VisitAllCookies takes a visitor which loops
- // on each (request's) cookies' name and value.
- VisitAllCookies(visitor func(name string, value string))
- // MaxAge returns the "cache-control" request header's value
- // seconds as int64
- // if header not found or parse failed then it returns -1.
- MaxAge() int64
- // +------------------------------------------------------------+
- // | Advanced: Response Recorder and Transactions |
- // +------------------------------------------------------------+
- // 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.
- Record()
- // Recorder returns the context's ResponseRecorder
- // if not recording then it starts recording and returns the new context's ResponseRecorder
- Recorder() *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.
- IsRecording() (*ResponseRecorder, bool)
- // BeginTransaction starts a scoped transaction.
- //
- // You can search third-party articles or books on how Business Transaction works (it's quite simple, especially here).
- //
- // 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.
- // Transactions have their own middleware ecosystem also, look iris.go:UseTransaction.
- //
- // See https://github.com/kataras/iris/tree/master/_examples/ for more
- BeginTransaction(pipe func(t *Transaction))
- // SkipTransactions if called then skip the rest of the transactions
- // or all of them if called before the first transaction
- SkipTransactions()
- // TransactionsSkipped returns true if the transactions skipped or canceled at all.
- TransactionsSkipped() bool
- // Exec calls the `context/Application#ServeCtx`
- // based on this context but with a changed method and path
- // 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.
- //
- // You can find the Route by app.GetRoute("theRouteName")
- // you can set a route name as: myRoute := app.Get("/mypath", handler)("theRouteName")
- // that will set a name to the route and returns its RouteInfo instance for further usage.
- //
- // It doesn't changes the global state, if a route was "offline" it remains offline.
- //
- // app.None(...) and app.GetRoutes().Offline(route)/.Online(route, method)
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/routing/route-state
- //
- // User can get the response by simple using rec := ctx.Recorder(); rec.Body()/rec.StatusCode()/rec.Header().
- //
- // Context's Values and the Session are kept in order to be able to communicate via the result route.
- //
- // It's for extreme use cases, 99% of the times will never be useful for you.
- Exec(method, path string)
- // RouteExists reports whether a particular route exists
- // It will search from the current subdomain of context's host, if not inside the root domain.
- RouteExists(method, path string) bool
- // Application returns the iris app instance which belongs to this context.
- // Worth to notice that this function returns an interface
- // of the Application, which contains methods that are safe
- // to be executed at serve-time. The full app's fields
- // and methods are not available here for the developer's safety.
- Application() Application
- // String returns the string representation of this request.
- // Each context has a unique string representation.
- // It can be used for simple debugging scenarios, i.e print context as string.
- //
- // What it returns? A number which declares the length of the
- // total `String` calls per executable application, followed
- // by the remote IP (the client) and finally the method:url.
- String() string
- }
- var _ Context = (*context)(nil)
- // Do calls the SetHandlers(handlers)
- // and executes the first handler,
- // handlers should not be empty.
- //
- // It's used by the router, developers may use that
- // to replace and execute handlers immediately.
- func Do(ctx Context, handlers Handlers) {
- if len(handlers) > 0 {
- ctx.SetHandlers(handlers)
- handlers[0](ctx)
- }
- }
- // LimitRequestBodySize is a middleware which sets a request body size limit
- // for all next handlers in the chain.
- var LimitRequestBodySize = func(maxRequestBodySizeBytes int64) Handler {
- return func(ctx Context) {
- ctx.SetMaxRequestBodySize(maxRequestBodySizeBytes)
- ctx.Next()
- }
- }
- // Gzip is a middleware which enables writing
- // using gzip compression, if client supports.
- var Gzip = func(ctx Context) {
- ctx.Gzip(true)
- ctx.Next()
- }
- // Map is just a shortcut of the map[string]interface{}.
- type Map map[string]interface{}
- // +------------------------------------------------------------+
- // | Context Implementation |
- // +------------------------------------------------------------+
- type context struct {
- // the unique id, it's zero until `String` function is called,
- // it's here to cache the random, unique context's id, although `String`
- // returns more than this.
- id uint64
- // the http.ResponseWriter wrapped by custom writer.
- writer ResponseWriter
- // the original http.Request
- request *http.Request
- // the current route's name registered to this request path.
- currentRouteName string
- // the local key-value storage
- params RequestParams // url named parameters.
- values memstore.Store // generic storage, middleware communication.
- // the underline application app.
- app Application
- // the route's handlers
- handlers Handlers
- // the current position of the handler's chain
- currentHandlerIndex int
- }
- // NewContext returns the default, internal, context implementation.
- // You may use this function to embed the default context implementation
- // to a custom one.
- //
- // This context is received by the context pool.
- func NewContext(app Application) Context {
- return &context{app: app}
- }
- // BeginRequest is executing once for each request
- // it should prepare the (new or acquired from pool) context's fields for the new request.
- //
- // To follow the iris' flow, developer should:
- // 1. reset handlers to nil
- // 2. reset store to empty
- // 3. reset sessions to nil
- // 4. reset response writer to the http.ResponseWriter
- // 5. reset request to the *http.Request
- // and any other optional steps, depends on dev's application type.
- func (ctx *context) BeginRequest(w http.ResponseWriter, r *http.Request) {
- ctx.handlers = nil // will be filled by router.Serve/HTTP
- ctx.values = ctx.values[0:0] // >> >> by context.Values().Set
- ctx.params.store = ctx.params.store[0:0]
- ctx.request = r
- ctx.currentHandlerIndex = 0
- ctx.writer = AcquireResponseWriter()
- ctx.writer.BeginResponse(w)
- }
- // StatusCodeNotSuccessful defines if a specific "statusCode" is not
- // a valid status code for a successful response.
- // It defaults to < 200 || >= 400
- //
- // Read more at `iris#DisableAutoFireStatusCode`, `iris/core/router#ErrorCodeHandler`
- // and `iris/core/router#OnAnyErrorCode` for relative information.
- //
- // Do NOT change it.
- //
- // It's exported for extreme situations--special needs only, when the Iris server and the client
- // is not following the RFC: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
- var StatusCodeNotSuccessful = func(statusCode int) bool {
- return statusCode < 200 || statusCode >= 400
- }
- // EndRequest is executing once after a response to the request was sent and this context is useless or released.
- //
- // To follow the iris' flow, developer should:
- // 1. flush the response writer's result
- // 2. release the response writer
- // and any other optional steps, depends on dev's application type.
- func (ctx *context) EndRequest() {
- if StatusCodeNotSuccessful(ctx.GetStatusCode()) &&
- !ctx.Application().ConfigurationReadOnly().GetDisableAutoFireStatusCode() {
- // author's note:
- // if recording, the error handler can handle
- // the rollback and remove any response written before,
- // we don't have to do anything here, written is <=0 (-1 for default empty, even no status code)
- // when Recording
- // because we didn't flush the response yet
- // if !recording then check if the previous handler didn't send something
- // to the client.
- if ctx.writer.Written() <= 0 {
- // Author's notes:
- // previously: == -1,
- // <=0 means even if empty write called which has meaning;
- // rel: core/router/status.go#Fire-else
- // mvc/activator/funcmethod/func_result_dispatcher.go#DispatchCommon-write
- // mvc/response.go#defaultFailureResponse - no text given but
- // status code should be fired, but it couldn't because of the .Write
- // action, the .Written() was 0 even on empty response, this 0 means that
- // a status code given, the previous check of the "== -1" didn't make check for that,
- // we do now.
- ctx.Application().FireErrorCode(ctx)
- }
- }
- ctx.writer.FlushResponse()
- ctx.writer.EndResponse()
- }
- // ResponseWriter returns an http.ResponseWriter compatible response writer, as expected.
- func (ctx *context) ResponseWriter() ResponseWriter {
- return ctx.writer
- }
- // ResetResponseWriter should change or upgrade the context's ResponseWriter.
- func (ctx *context) ResetResponseWriter(newResponseWriter ResponseWriter) {
- ctx.writer = newResponseWriter
- }
- // Request returns the original *http.Request, as expected.
- func (ctx *context) Request() *http.Request {
- return ctx.request
- }
- // SetCurrentRouteName sets the route's name internally,
- // in order to be able to find the correct current "read-only" Route when
- // end-developer calls the `GetCurrentRoute()` function.
- // It's being initialized by the Router, if you change that name
- // manually nothing really happens except that you'll get other
- // route via `GetCurrentRoute()`.
- // Instead, to execute a different path
- // from this context you should use the `Exec` function
- // or change the handlers via `SetHandlers/AddHandler` functions.
- func (ctx *context) SetCurrentRouteName(currentRouteName string) {
- ctx.currentRouteName = currentRouteName
- }
- // GetCurrentRoute returns the current registered "read-only" route that
- // was being registered to this request's path.
- func (ctx *context) GetCurrentRoute() RouteReadOnly {
- return ctx.app.GetRouteReadOnly(ctx.currentRouteName)
- }
- // Do calls the SetHandlers(handlers)
- // and executes the first handler,
- // handlers should not be empty.
- //
- // It's used by the router, developers may use that
- // to replace and execute handlers immediately.
- func (ctx *context) Do(handlers Handlers) {
- Do(ctx, handlers)
- }
- // AddHandler can add handler(s)
- // to the current request in serve-time,
- // these handlers are not persistenced to the router.
- //
- // Router is calling this function to add the route's handler.
- // If AddHandler called then the handlers will be inserted
- // to the end of the already-defined route's handler.
- //
- func (ctx *context) AddHandler(handlers ...Handler) {
- ctx.handlers = append(ctx.handlers, handlers...)
- }
- // SetHandlers replaces all handlers with the new.
- func (ctx *context) SetHandlers(handlers Handlers) {
- ctx.handlers = handlers
- }
- // Handlers keeps tracking of the current handlers.
- func (ctx *context) Handlers() Handlers {
- return ctx.handlers
- }
- // HandlerIndex sets the current index of the
- // current context's handlers chain.
- // If -1 passed then it just returns the
- // current handler index without change the current index.rns that index, useless return value.
- //
- // Look Handlers(), Next() and StopExecution() too.
- func (ctx *context) HandlerIndex(n int) (currentIndex int) {
- if n < 0 || n > len(ctx.handlers)-1 {
- return ctx.currentHandlerIndex
- }
- ctx.currentHandlerIndex = n
- return n
- }
- // Proceed is an alternative way to check if a particular handler
- // has been executed and called the `ctx.Next` function inside it.
- // This is useful only when you run a handler inside
- // another handler. It justs checks for before index and the after index.
- //
- // A usecase example is when you want to execute a middleware
- // inside controller's `BeginRequest` that calls the `ctx.Next` inside it.
- // The Controller looks the whole flow (BeginRequest, method handler, EndRequest)
- // as one handler, so `ctx.Next` will not be reflected to the method handler
- // if called from the `BeginRequest`.
- //
- // Although `BeginRequest` should NOT be used to call other handlers,
- // the `BeginRequest` has been introduced to be able to set
- // common data to all method handlers before their execution.
- // Controllers can accept middleware(s) from the MVC's Application's Router as normally.
- //
- // That said let's see an example of `ctx.Proceed`:
- //
- // var authMiddleware = basicauth.New(basicauth.Config{
- // Users: map[string]string{
- // "admin": "password",
- // },
- // })
- //
- // func (c *UsersController) BeginRequest(ctx iris.Context) {
- // if !ctx.Proceed(authMiddleware) {
- // ctx.StopExecution()
- // }
- // }
- // This Get() will be executed in the same handler as `BeginRequest`,
- // internally controller checks for `ctx.StopExecution`.
- // So it will not be fired if BeginRequest called the `StopExecution`.
- // func(c *UsersController) Get() []models.User {
- // return c.Service.GetAll()
- //}
- // Alternative way is `!ctx.IsStopped()` if middleware make use of the `ctx.StopExecution()` on failure.
- func (ctx *context) Proceed(h Handler) bool {
- beforeIdx := ctx.currentHandlerIndex
- h(ctx)
- if ctx.currentHandlerIndex > beforeIdx && !ctx.IsStopped() {
- return true
- }
- return false
- }
- // HandlerName returns the current handler's name, helpful for debugging.
- func (ctx *context) HandlerName() string {
- return HandlerName(ctx.handlers[ctx.currentHandlerIndex])
- }
- // Next is the function that executed when `ctx.Next()` is called.
- // It can be changed to a customized one if needed (very advanced usage).
- //
- // See `DefaultNext` for more information about this and why it's exported like this.
- var Next = DefaultNext
- // DefaultNext is the default function that executed on each middleware if `ctx.Next()`
- // is called.
- //
- // DefaultNext calls the next handler from the handlers chain by registration order,
- // it should be used inside a middleware.
- //
- // It can be changed to a customized one if needed (very advanced usage).
- //
- // Developers are free to customize the whole or part of the Context's implementation
- // by implementing a new `context.Context` (see https://github.com/kataras/iris/tree/master/_examples/routing/custom-context)
- // or by just override the `context.Next` package-level field, `context.DefaultNext` is exported
- // in order to be able for developers to merge your customized version one with the default behavior as well.
- func DefaultNext(ctx Context) {
- if ctx.IsStopped() {
- return
- }
- if n, handlers := ctx.HandlerIndex(-1)+1, ctx.Handlers(); n < len(handlers) {
- ctx.HandlerIndex(n)
- handlers[n](ctx)
- }
- }
- // Next calls all the next handler from the handlers chain,
- // it should be used inside a middleware.
- //
- // Note: Custom context should override this method in order to be able to pass its own context.Context implementation.
- func (ctx *context) Next() { // or context.Next(ctx)
- Next(ctx)
- }
- // NextOr checks if chain has a next handler, if so then it executes it
- // otherwise it sets a new chain assigned to this Context based on the given handler(s)
- // and executes its first handler.
- //
- // Returns true if next handler exists and executed, otherwise false.
- //
- // Note that if no next handler found and handlers are missing then
- // it sends a Status Not Found (404) to the client and it stops the execution.
- func (ctx *context) NextOr(handlers ...Handler) bool {
- if next := ctx.NextHandler(); next != nil {
- next(ctx)
- ctx.Skip() // skip this handler from the chain.
- return true
- }
- if len(handlers) == 0 {
- ctx.NotFound()
- ctx.StopExecution()
- return false
- }
- ctx.Do(handlers)
- return false
- }
- // NextOrNotFound checks if chain has a next handler, if so then it executes it
- // otherwise it sends a Status Not Found (404) to the client and stops the execution.
- //
- // Returns true if next handler exists and executed, otherwise false.
- func (ctx *context) NextOrNotFound() bool { return ctx.NextOr() }
- // NextHandler returns (it doesn't execute) the next handler from the handlers chain.
- //
- // Use .Skip() to skip this handler if needed to execute the next of this returning handler.
- func (ctx *context) NextHandler() Handler {
- if ctx.IsStopped() {
- return nil
- }
- nextIndex := ctx.currentHandlerIndex + 1
- // check if it has a next middleware
- if nextIndex < len(ctx.handlers) {
- return ctx.handlers[nextIndex]
- }
- return nil
- }
- // Skip skips/ignores the next handler from the handlers chain,
- // it should be used inside a middleware.
- func (ctx *context) Skip() {
- ctx.HandlerIndex(ctx.currentHandlerIndex + 1)
- }
- const stopExecutionIndex = -1 // I don't set to a max value because we want to be able to reuse the handlers even if stopped with .Skip
- // StopExecution if called then the following .Next calls are ignored,
- // as a result the next handlers in the chain will not be fire.
- func (ctx *context) StopExecution() {
- ctx.currentHandlerIndex = stopExecutionIndex
- }
- // IsStopped checks and returns true if the current position of the context is -1,
- // means that the StopExecution() was called.
- func (ctx *context) IsStopped() bool {
- return ctx.currentHandlerIndex == stopExecutionIndex
- }
- // OnConnectionClose registers the "cb" function which will fire (on its own goroutine, no need to be registered goroutine by the end-dev)
- // when the underlying connection has gone away.
- //
- // This mechanism can be used to cancel long operations on the server
- // if the client has disconnected before the response is ready.
- //
- // It depends on the `http#CloseNotify`.
- // CloseNotify may wait to notify until Request.Body has been
- // fully read.
- //
- // After the main Handler has returned, there is no guarantee
- // that the channel receives a value.
- //
- // Finally, it reports whether the protocol supports pipelines (HTTP/1.1 with pipelines disabled is not supported).
- // The "cb" will not fire for sure if the output value is false.
- //
- // Note that you can register only one callback for the entire request handler chain/per route.
- //
- // Look the `ResponseWriter#CloseNotifier` for more.
- func (ctx *context) OnConnectionClose(cb func()) bool {
- // Note that `ctx.ResponseWriter().CloseNotify()` can already do the same
- // but it returns a channel which will never fire if it the protocol version is not compatible,
- // here we don't want to allocate an empty channel, just skip it.
- notifier, ok := ctx.writer.CloseNotifier()
- if !ok {
- return false
- }
- notify := notifier.CloseNotify()
- go func() {
- <-notify
- if cb != nil {
- cb()
- }
- }()
- return true
- }
- // OnClose registers the callback function "cb" to the underline connection closing event using the `Context#OnConnectionClose`
- // and also in the end of the request handler using the `ResponseWriter#SetBeforeFlush`.
- // Note that you can register only one callback for the entire request handler chain/per route.
- //
- // Look the `Context#OnConnectionClose` and `ResponseWriter#SetBeforeFlush` for more.
- func (ctx *context) OnClose(cb func()) {
- if cb == nil {
- return
- }
- // Register the on underline connection close handler first.
- ctx.OnConnectionClose(cb)
- // Author's notes:
- // This is fired on `ctx.ResponseWriter().FlushResponse()` which is fired by the framework automatically, internally, on the end of request handler(s),
- // it is not fired on the underline streaming function of the writer: `ctx.ResponseWriter().Flush()` (which can be fired more than one if streaming is supported by the client).
- // The `FlushResponse` is called only once, so add the "cb" here, no need to add done request handlers each time `OnClose` is called by the end-dev.
- //
- // Don't allow more than one because we don't allow that on `OnConnectionClose` too:
- // old := ctx.writer.GetBeforeFlush()
- // if old != nil {
- // ctx.writer.SetBeforeFlush(func() {
- // old()
- // cb()
- // })
- // return
- // }
- ctx.writer.SetBeforeFlush(cb)
- }
- // +------------------------------------------------------------+
- // | Current "user/request" storage |
- // | and share information between the handlers - Values(). |
- // | Save and get named path parameters - Params() |
- // +------------------------------------------------------------+
- // Params returns the current url's named parameters key-value storage.
- // Named path parameters are being saved here.
- // This storage, as the whole context, is per-request lifetime.
- func (ctx *context) Params() *RequestParams {
- return &ctx.params
- }
- // Values returns the current "user" storage.
- // Named path parameters and any optional data can be saved here.
- // This storage, as the whole context, is per-request lifetime.
- //
- // You can use this function to Set and Get local values
- // that can be used to share information between handlers and middleware.
- func (ctx *context) Values() *memstore.Store {
- return &ctx.values
- }
- // Translate is the i18n (localization) middleware's function,
- // it calls the Get("translate") to return the translated value.
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/miscellaneous/i18n
- func (ctx *context) Translate(format string, args ...interface{}) string {
- if cb, ok := ctx.values.Get(ctx.Application().ConfigurationReadOnly().GetTranslateFunctionContextKey()).(func(format string, args ...interface{}) string); ok {
- return cb(format, args...)
- }
- return ""
- }
- // +------------------------------------------------------------+
- // | Path, Host, Subdomain, IP, Headers etc... |
- // +------------------------------------------------------------+
- // Method returns the request.Method, the client's http method to the server.
- func (ctx *context) Method() string {
- return ctx.request.Method
- }
- // Path returns the full request path,
- // escaped if EnablePathEscape config field is true.
- func (ctx *context) Path() string {
- return ctx.RequestPath(ctx.Application().ConfigurationReadOnly().GetEnablePathEscape())
- }
- // DecodeQuery returns the uri parameter as url (string)
- // useful when you want to pass something to a database and be valid to retrieve it via context.Param
- // use it only for special cases, when the default behavior doesn't suits you.
- //
- // http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
- // it uses just the url.QueryUnescape
- func DecodeQuery(path string) string {
- if path == "" {
- return ""
- }
- encodedPath, err := url.QueryUnescape(path)
- if err != nil {
- return path
- }
- return encodedPath
- }
- // DecodeURL returns the decoded uri
- // useful when you want to pass something to a database and be valid to retrieve it via context.Param
- // use it only for special cases, when the default behavior doesn't suits you.
- //
- // http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
- // it uses just the url.Parse
- func DecodeURL(uri string) string {
- u, err := url.Parse(uri)
- if err != nil {
- return uri
- }
- return u.String()
- }
- // RequestPath returns the full request path,
- // based on the 'escape'.
- func (ctx *context) RequestPath(escape bool) string {
- if escape {
- return DecodeQuery(ctx.request.URL.EscapedPath())
- }
- return ctx.request.URL.Path // RawPath returns empty, requesturi can be used instead also.
- }
- // PathPrefixMap accepts a map of string and a handler.
- // The key of "m" is the key, which is the prefix, regular expressions are not valid.
- // The value of "m" is the handler that will be executed if HasPrefix(context.Path).
- // func (ctx *context) PathPrefixMap(m map[string]context.Handler) bool {
- // path := ctx.Path()
- // for k, v := range m {
- // if strings.HasPrefix(path, k) {
- // v(ctx)
- // return true
- // }
- // }
- // return false
- // } no, it will not work because map is a random peek data structure.
- // Host returns the host part of the current URI.
- func (ctx *context) Host() string {
- return GetHost(ctx.request)
- }
- // GetHost returns the host part of the current URI.
- func GetHost(r *http.Request) string {
- h := r.URL.Host
- if h == "" {
- h = r.Host
- }
- return h
- }
- // Subdomain returns the subdomain of this request, if any.
- // Note that this is a fast method which does not cover all cases.
- func (ctx *context) Subdomain() (subdomain string) {
- host := ctx.Host()
- if index := strings.IndexByte(host, '.'); index > 0 {
- subdomain = host[0:index]
- }
- // listening on mydomain.com:80
- // subdomain = mydomain, but it's wrong, it should return ""
- vhost := ctx.Application().ConfigurationReadOnly().GetVHost()
- if strings.Contains(vhost, subdomain) { // then it's not subdomain
- return ""
- }
- return
- }
- // IsWWW returns true if the current subdomain (if any) is www.
- func (ctx *context) IsWWW() bool {
- host := ctx.Host()
- if index := strings.IndexByte(host, '.'); index > 0 {
- // if it has a subdomain and it's www then return true.
- if subdomain := host[0:index]; !strings.Contains(ctx.Application().ConfigurationReadOnly().GetVHost(), subdomain) {
- return subdomain == "www"
- }
- }
- return false
- }
- const xForwardedForHeaderKey = "X-Forwarded-For"
- // RemoteAddr tries to parse and return the real client's request IP.
- //
- // Based on allowed headers names that can be modified from Configuration.RemoteAddrHeaders.
- //
- // If parse based on these headers fail then it will return the Request's `RemoteAddr` field
- // which is filled by the server before the HTTP handler.
- //
- // Look `Configuration.RemoteAddrHeaders`,
- // `Configuration.WithRemoteAddrHeader(...)`,
- // `Configuration.WithoutRemoteAddrHeader(...)` for more.
- func (ctx *context) RemoteAddr() string {
- remoteHeaders := ctx.Application().ConfigurationReadOnly().GetRemoteAddrHeaders()
- for headerName, enabled := range remoteHeaders {
- if enabled {
- headerValue := ctx.GetHeader(headerName)
- // exception needed for 'X-Forwarded-For' only , if enabled.
- if headerName == xForwardedForHeaderKey {
- idx := strings.IndexByte(headerValue, ',')
- if idx >= 0 {
- headerValue = headerValue[0:idx]
- }
- }
- realIP := strings.TrimSpace(headerValue)
- if realIP != "" {
- return realIP
- }
- }
- }
- addr := strings.TrimSpace(ctx.request.RemoteAddr)
- if addr != "" {
- // 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
- }
- // GetHeader returns the request header's value based on its name.
- func (ctx *context) GetHeader(name string) string {
- return ctx.request.Header.Get(name)
- }
- // IsAjax returns true if this request is an 'ajax request'( XMLHttpRequest)
- //
- // There is no a 100% way of knowing that a request was made via Ajax.
- // You should never trust data coming from the client, they can be easily overcome by spoofing.
- //
- // Note that "X-Requested-With" Header can be modified by any client(because of "X-"),
- // so don't rely on IsAjax for really serious stuff,
- // try to find another way of detecting the type(i.e, content type),
- // there are many blogs that describe these problems and provide different kind of solutions,
- // it's always depending on the application you're building,
- // this is the reason why this `IsAjax`` is simple enough for general purpose use.
- //
- // Read more at: https://developer.mozilla.org/en-US/docs/AJAX
- // and https://xhr.spec.whatwg.org/
- func (ctx *context) IsAjax() bool {
- return ctx.GetHeader("X-Requested-With") == "XMLHttpRequest"
- }
- var isMobileRegex = regexp.MustCompile(`(?i)(android|avantgo|blackberry|bolt|boost|cricket|docomo|fone|hiptop|mini|mobi|palm|phone|pie|tablet|up\.browser|up\.link|webos|wos)`)
- // IsMobile checks if client is using a mobile device(phone or tablet) to communicate with this server.
- // If the return value is true that means that the http client using a mobile
- // device to communicate with the server, otherwise false.
- //
- // Keep note that this checks the "User-Agent" request header.
- func (ctx *context) IsMobile() bool {
- s := ctx.GetHeader("User-Agent")
- return isMobileRegex.MatchString(s)
- }
- type (
- // Referrer contains the extracted information from the `GetReferrer`
- //
- // The structure contains struct tags for JSON, form, XML, YAML and TOML.
- // Look the `GetReferrer() Referrer` and `goreferrer` external package.
- Referrer struct {
- Type ReferrerType `json:"type" form:"referrer_type" xml:"Type" yaml:"Type" toml:"Type"`
- Label string `json:"label" form:"referrer_form" xml:"Label" yaml:"Label" toml:"Label"`
- URL string `json:"url" form:"referrer_url" xml:"URL" yaml:"URL" toml:"URL"`
- Subdomain string `json:"subdomain" form:"referrer_subdomain" xml:"Subdomain" yaml:"Subdomain" toml:"Subdomain"`
- Domain string `json:"domain" form:"referrer_domain" xml:"Domain" yaml:"Domain" toml:"Domain"`
- Tld string `json:"tld" form:"referrer_tld" xml:"Tld" yaml:"Tld" toml:"Tld"`
- Path string `jsonn:"path" form:"referrer_path" xml:"Path" yaml:"Path" toml:"Path"`
- Query string `json:"query" form:"referrer_query" xml:"Query" yaml:"Query" toml:"GoogleType"`
- GoogleType ReferrerGoogleSearchType `json:"googleType" form:"referrer_google_type" xml:"GoogleType" yaml:"GoogleType" toml:"GoogleType"`
- }
- // ReferrerType is the goreferrer enum for a referrer type (indirect, direct, email, search, social).
- ReferrerType int
- // ReferrerGoogleSearchType is the goreferrer enum for a google search type (organic, adwords).
- ReferrerGoogleSearchType int
- )
- // Contains the available values of the goreferrer enums.
- const (
- ReferrerInvalid ReferrerType = iota
- ReferrerIndirect
- ReferrerDirect
- ReferrerEmail
- ReferrerSearch
- ReferrerSocial
- ReferrerNotGoogleSearch ReferrerGoogleSearchType = iota
- ReferrerGoogleOrganicSearch
- ReferrerGoogleAdwords
- )
- func (gs ReferrerGoogleSearchType) String() string {
- return goreferrer.GoogleSearchType(gs).String()
- }
- func (r ReferrerType) String() string {
- return goreferrer.ReferrerType(r).String()
- }
- // unnecessary but good to know the default values upfront.
- var emptyReferrer = Referrer{Type: ReferrerInvalid, GoogleType: ReferrerNotGoogleSearch}
- // GetReferrer extracts and returns the information from the "Referer" header as specified
- // in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
- // or by the URL query parameter "referer".
- func (ctx *context) GetReferrer() Referrer {
- // the underline net/http follows the https://tools.ietf.org/html/rfc7231#section-5.5.2,
- // so there is nothing special left to do.
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
- refURL := ctx.GetHeader("Referer")
- if refURL == "" {
- refURL = ctx.URLParam("referer")
- }
- if ref := goreferrer.DefaultRules.Parse(refURL); ref.Type > goreferrer.Invalid {
- return Referrer{
- Type: ReferrerType(ref.Type),
- Label: ref.Label,
- URL: ref.URL,
- Subdomain: ref.Subdomain,
- Domain: ref.Domain,
- Tld: ref.Tld,
- Path: ref.Path,
- Query: ref.Query,
- GoogleType: ReferrerGoogleSearchType(ref.GoogleType),
- }
- }
- return emptyReferrer
- }
- // +------------------------------------------------------------+
- // | Response Headers helpers |
- // +------------------------------------------------------------+
- // Header adds a header to the response, if value is empty
- // it removes the header by its name.
- func (ctx *context) Header(name string, value string) {
- if value == "" {
- ctx.writer.Header().Del(name)
- return
- }
- ctx.writer.Header().Add(name, value)
- }
- // ContentType sets the response writer's header key "Content-Type" to the 'cType'.
- func (ctx *context) ContentType(cType string) {
- if cType == "" {
- return
- }
- // 1. if it's path or a filename or an extension,
- // then take the content type from that
- if strings.Contains(cType, ".") {
- ext := filepath.Ext(cType)
- cType = mime.TypeByExtension(ext)
- }
- // if doesn't contain a charset already then append it
- if !strings.Contains(cType, "charset") {
- if cType != ContentBinaryHeaderValue {
- cType += "; charset=" + ctx.Application().ConfigurationReadOnly().GetCharset()
- }
- }
- ctx.writer.Header().Set(ContentTypeHeaderKey, cType)
- }
- // GetContentType returns the response writer's header value of "Content-Type"
- // which may, setted before with the 'ContentType'.
- func (ctx *context) GetContentType() string {
- return ctx.writer.Header().Get(ContentTypeHeaderKey)
- }
- // GetContentType returns the request's header value of "Content-Type".
- func (ctx *context) GetContentTypeRequested() string {
- return ctx.GetHeader(ContentTypeHeaderKey)
- }
- // GetContentLength returns the request's header value of "Content-Length".
- // Returns 0 if header was unable to be found or its value was not a valid number.
- func (ctx *context) GetContentLength() int64 {
- if v := ctx.GetHeader(ContentLengthHeaderKey); v != "" {
- n, _ := strconv.ParseInt(v, 10, 64)
- return n
- }
- return 0
- }
- // StatusCode sets the status code header to the response.
- // Look .GetStatusCode & .FireStatusCode too.
- //
- // Remember, the last one before .Write matters except recorder and transactions.
- func (ctx *context) StatusCode(statusCode int) {
- ctx.writer.WriteHeader(statusCode)
- }
- // NotFound emits an error 404 to the client, using the specific custom error error handler.
- // Note that you may need to call ctx.StopExecution() if you don't want the next handlers
- // to be executed. Next handlers are being executed on iris because you can alt the
- // error code and change it to a more specific one, i.e
- // users := app.Party("/users")
- // users.Done(func(ctx context.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }})
- func (ctx *context) NotFound() {
- ctx.StatusCode(http.StatusNotFound)
- }
- // GetStatusCode returns the current status code of the response.
- // Look StatusCode too.
- func (ctx *context) GetStatusCode() int {
- return ctx.writer.StatusCode()
- }
- // +------------------------------------------------------------+
- // | Various Request and Post Data |
- // +------------------------------------------------------------+
- // URLParam returns true if the url parameter exists, otherwise false.
- func (ctx *context) URLParamExists(name string) bool {
- if q := ctx.request.URL.Query(); q != nil {
- _, exists := q[name]
- return exists
- }
- return false
- }
- // URLParamDefault returns the get parameter from a request, if not found then "def" is returned.
- func (ctx *context) URLParamDefault(name string, def string) string {
- if v := ctx.request.URL.Query().Get(name); v != "" {
- return v
- }
- return def
- }
- // URLParam returns the get parameter from a request, if any.
- func (ctx *context) URLParam(name string) string {
- return ctx.URLParamDefault(name, "")
- }
- // URLParamTrim returns the url query parameter with trailing white spaces removed from a request.
- func (ctx *context) URLParamTrim(name string) string {
- return strings.TrimSpace(ctx.URLParam(name))
- }
- // URLParamTrim returns the escaped url query parameter from a request.
- func (ctx *context) URLParamEscape(name string) string {
- return DecodeQuery(ctx.URLParam(name))
- }
- var errURLParamNotFound = errors.New("url param '%s' does not exist")
- // URLParamInt returns the url query parameter as int value from a request,
- // returns -1 and an error if parse failed or not found.
- func (ctx *context) URLParamInt(name string) (int, error) {
- if v := ctx.URLParam(name); v != "" {
- n, err := strconv.Atoi(v)
- if err != nil {
- return -1, err
- }
- return n, nil
- }
- return -1, errURLParamNotFound.Format(name)
- }
- // URLParamIntDefault returns the url query parameter as int value from a request,
- // if not found or parse failed then "def" is returned.
- func (ctx *context) URLParamIntDefault(name string, def int) int {
- v, err := ctx.URLParamInt(name)
- if err != nil {
- return def
- }
- return v
- }
- // URLParamInt32Default returns the url query parameter as int32 value from a request,
- // if not found or parse failed then "def" is returned.
- func (ctx *context) URLParamInt32Default(name string, def int32) int32 {
- if v := ctx.URLParam(name); v != "" {
- n, err := strconv.ParseInt(v, 10, 32)
- if err != nil {
- return def
- }
- return int32(n)
- }
- return def
- }
- // URLParamInt64 returns the url query parameter as int64 value from a request,
- // returns -1 and an error if parse failed or not found.
- func (ctx *context) URLParamInt64(name string) (int64, error) {
- if v := ctx.URLParam(name); v != "" {
- n, err := strconv.ParseInt(v, 10, 64)
- if err != nil {
- return -1, err
- }
- return n, nil
- }
- return -1, errURLParamNotFound.Format(name)
- }
- // URLParamInt64Default returns the url query parameter as int64 value from a request,
- // if not found or parse failed then "def" is returned.
- func (ctx *context) URLParamInt64Default(name string, def int64) int64 {
- v, err := ctx.URLParamInt64(name)
- if err != nil {
- return def
- }
- return v
- }
- // URLParamFloat64 returns the url query parameter as float64 value from a request,
- // returns an error and -1 if parse failed.
- func (ctx *context) URLParamFloat64(name string) (float64, error) {
- if v := ctx.URLParam(name); v != "" {
- n, err := strconv.ParseFloat(v, 64)
- if err != nil {
- return -1, err
- }
- return n, nil
- }
- return -1, errURLParamNotFound.Format(name)
- }
- // URLParamFloat64Default returns the url query parameter as float64 value from a request,
- // if not found or parse failed then "def" is returned.
- func (ctx *context) URLParamFloat64Default(name string, def float64) float64 {
- v, err := ctx.URLParamFloat64(name)
- if err != nil {
- return def
- }
- return v
- }
- // URLParamBool returns the url query parameter as boolean value from a request,
- // returns an error if parse failed.
- func (ctx *context) URLParamBool(name string) (bool, error) {
- return strconv.ParseBool(ctx.URLParam(name))
- }
- // URLParams returns a map of GET query parameters separated by comma if more than one
- // it returns an empty map if nothing found.
- func (ctx *context) URLParams() map[string]string {
- values := map[string]string{}
- q := ctx.request.URL.Query()
- if q != nil {
- for k, v := range q {
- values[k] = strings.Join(v, ",")
- }
- }
- return values
- }
- // No need anymore, net/http checks for the Form already.
- // func (ctx *context) askParseForm() error {
- // if ctx.request.Form == nil {
- // if err := ctx.request.ParseForm(); err != nil {
- // return err
- // }
- // }
- // return nil
- // }
- // FormValueDefault returns a single parsed form value by its "name",
- // including both the URL field's query parameters and the POST or PUT form data.
- //
- // Returns the "def" if not found.
- func (ctx *context) FormValueDefault(name string, def string) string {
- if form, has := ctx.form(); has {
- if v := form[name]; len(v) > 0 {
- return v[0]
- }
- }
- return def
- }
- // FormValue returns a single parsed form value by its "name",
- // including both the URL field's query parameters and the POST or PUT form data.
- func (ctx *context) FormValue(name string) string {
- return ctx.FormValueDefault(name, "")
- }
- // FormValues returns the parsed form data, including both the URL
- // field's query parameters and the POST or PUT form data.
- //
- // The default form's memory maximum size is 32MB, it can be changed by the
- // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
- // NOTE: A check for nil is necessary.
- func (ctx *context) FormValues() map[string][]string {
- form, _ := ctx.form()
- return form
- }
- // Form contains the parsed form data, including both the URL
- // field's query parameters and the POST or PUT form data.
- func (ctx *context) form() (form map[string][]string, found bool) {
- /*
- net/http/request.go#1219
- for k, v := range f.Value {
- r.Form[k] = append(r.Form[k], v...)
- // r.PostForm should also be populated. See Issue 9305.
- r.PostForm[k] = append(r.PostForm[k], v...)
- }
- */
- // ParseMultipartForm calls `request.ParseForm` automatically
- // therefore we don't need to call it here, although it doesn't hurt.
- // After one call to ParseMultipartForm or ParseForm,
- // subsequent calls have no effect, are idempotent.
- ctx.request.ParseMultipartForm(ctx.Application().ConfigurationReadOnly().GetPostMaxMemory())
- if form := ctx.request.Form; len(form) > 0 {
- return form, true
- }
- if form := ctx.request.PostForm; len(form) > 0 {
- return form, true
- }
- if m := ctx.request.MultipartForm; m != nil {
- if len(m.Value) > 0 {
- return m.Value, true
- }
- }
- return nil, false
- }
- // PostValueDefault returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name".
- //
- // If not found then "def" is returned instead.
- func (ctx *context) PostValueDefault(name string, def string) string {
- ctx.form()
- if v := ctx.request.PostForm[name]; len(v) > 0 {
- return v[0]
- }
- return def
- }
- // PostValue returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name"
- func (ctx *context) PostValue(name string) string {
- return ctx.PostValueDefault(name, "")
- }
- // PostValueTrim returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", without trailing spaces.
- func (ctx *context) PostValueTrim(name string) string {
- return strings.TrimSpace(ctx.PostValue(name))
- }
- var errUnableToFindPostValue = errors.New("unable to find post value '%s'")
- // PostValueInt returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", as int.
- //
- // If not found returns -1 and a non-nil error.
- func (ctx *context) PostValueInt(name string) (int, error) {
- v := ctx.PostValue(name)
- if v == "" {
- return -1, errUnableToFindPostValue.Format(name)
- }
- return strconv.Atoi(v)
- }
- // PostValueIntDefault returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", as int.
- //
- // If not found or parse errors returns the "def".
- func (ctx *context) PostValueIntDefault(name string, def int) int {
- if v, err := ctx.PostValueInt(name); err == nil {
- return v
- }
- return def
- }
- // PostValueInt64 returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", as float64.
- //
- // If not found returns -1 and a non-nil error.
- func (ctx *context) PostValueInt64(name string) (int64, error) {
- v := ctx.PostValue(name)
- if v == "" {
- return -1, errUnableToFindPostValue.Format(name)
- }
- return strconv.ParseInt(v, 10, 64)
- }
- // PostValueInt64Default returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", as int64.
- //
- // If not found or parse errors returns the "def".
- func (ctx *context) PostValueInt64Default(name string, def int64) int64 {
- if v, err := ctx.PostValueInt64(name); err == nil {
- return v
- }
- return def
- }
- // PostValueInt64Default returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", as float64.
- //
- // If not found returns -1 and a non-nil error.
- func (ctx *context) PostValueFloat64(name string) (float64, error) {
- v := ctx.PostValue(name)
- if v == "" {
- return -1, errUnableToFindPostValue.Format(name)
- }
- return strconv.ParseFloat(v, 64)
- }
- // PostValueInt64Default returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", as float64.
- //
- // If not found or parse errors returns the "def".
- func (ctx *context) PostValueFloat64Default(name string, def float64) float64 {
- if v, err := ctx.PostValueFloat64(name); err == nil {
- return v
- }
- return def
- }
- // PostValueInt64Default returns the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name", as bool.
- //
- // If not found or value is false, then it returns false, otherwise true.
- func (ctx *context) PostValueBool(name string) (bool, error) {
- v := ctx.PostValue(name)
- if v == "" {
- return false, errUnableToFindPostValue.Format(name)
- }
- return strconv.ParseBool(v)
- }
- // PostValues returns all the parsed form data from POST, PATCH,
- // or PUT body parameters based on a "name" as a string slice.
- //
- // The default form's memory maximum size is 32MB, it can be changed by the
- // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
- func (ctx *context) PostValues(name string) []string {
- ctx.form()
- return ctx.request.PostForm[name]
- }
- // FormFile returns the first uploaded file that received from the client.
- //
- //
- // The default form's memory maximum size is 32MB, it can be changed by the
- // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/http_request/upload-file
- func (ctx *context) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
- // we don't have access to see if the request is body stream
- // and then the ParseMultipartForm can be useless
- // here but do it in order to apply the post limit,
- // the internal request.FormFile will not do it if that's filled
- // and it's not a stream body.
- if err := ctx.request.ParseMultipartForm(ctx.Application().ConfigurationReadOnly().GetPostMaxMemory()); err != nil {
- return nil, nil, err
- }
- return ctx.request.FormFile(key)
- }
- // UploadFormFiles uploads any received file(s) from the client
- // to the system physical location "destDirectory".
- //
- // The second optional argument "before" gives caller the chance to
- // modify the *miltipart.FileHeader before saving to the disk,
- // it can be used to change a file's name based on the current request,
- // all FileHeader's options can be changed. You can ignore it if
- // you don't need to use this capability before saving a file to the disk.
- //
- // Note that it doesn't check if request body streamed.
- //
- // Returns the copied length as int64 and
- // a not nil error if at least one new file
- // can't be created due to the operating system's permissions or
- // http.ErrMissingFile if no file received.
- //
- // If you want to receive & accept files and manage them manually you can use the `context#FormFile`
- // instead and create a copy function that suits your needs, the below is for generic usage.
- //
- // The default form's memory maximum size is 32MB, it can be changed by the
- // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
- //
- // See `FormFile` to a more controlled to receive a file.
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/http_request/upload-files
- func (ctx *context) UploadFormFiles(destDirectory string, before ...func(Context, *multipart.FileHeader)) (n int64, err error) {
- err = ctx.request.ParseMultipartForm(ctx.Application().ConfigurationReadOnly().GetPostMaxMemory())
- if err != nil {
- return 0, err
- }
- if ctx.request.MultipartForm != nil {
- if fhs := ctx.request.MultipartForm.File; fhs != nil {
- for _, files := range fhs {
- for _, file := range files {
- for _, b := range before {
- b(ctx, file)
- }
- n0, err0 := uploadTo(file, destDirectory)
- if err0 != nil {
- return 0, err0
- }
- n += n0
- }
- }
- return n, nil
- }
- }
- return 0, http.ErrMissingFile
- }
- func uploadTo(fh *multipart.FileHeader, destDirectory string) (int64, error) {
- src, err := fh.Open()
- if err != nil {
- return 0, err
- }
- defer src.Close()
- out, err := os.OpenFile(filepath.Join(destDirectory, fh.Filename),
- os.O_WRONLY|os.O_CREATE, os.FileMode(0666))
- if err != nil {
- return 0, err
- }
- defer out.Close()
- return io.Copy(out, src)
- }
- // Redirect sends a redirect response to the client
- // to a specific url or relative path.
- // accepts 2 parameters string and an optional int
- // first parameter is the url to redirect
- // second parameter is the http status should send,
- // default is 302 (StatusFound),
- // you can set it to 301 (Permant redirect)
- // or 303 (StatusSeeOther) if POST method,
- // or StatusTemporaryRedirect(307) if that's nessecery.
- func (ctx *context) Redirect(urlToRedirect string, statusHeader ...int) {
- ctx.StopExecution()
- // get the previous status code given by the end-developer.
- status := ctx.GetStatusCode()
- if status < 300 { // the previous is not a RCF-valid redirect status.
- status = 0
- }
- if len(statusHeader) > 0 {
- // check if status code is passed via receivers.
- if s := statusHeader[0]; s > 0 {
- status = s
- }
- }
- if status == 0 {
- // if status remains zero then default it.
- // a 'temporary-redirect-like' which works better than for our purpose
- status = http.StatusFound
- }
- http.Redirect(ctx.writer, ctx.request, urlToRedirect, status)
- }
- // +------------------------------------------------------------+
- // | Body Readers |
- // +------------------------------------------------------------+
- // 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.writer, ctx.request.Body, limitOverBytes)
- }
- // UnmarshalBody reads the request's body and binds it to a value or pointer of any type
- // Examples of usage: context.ReadJSON, context.ReadXML.
- //
- // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-custom-via-unmarshaler/main.go
- //
- // UnmarshalBody does not check about gzipped data.
- // Do not rely on compressed data incoming to your server. The main reason is: https://en.wikipedia.org/wiki/Zip_bomb
- // However you are still free to read the `ctx.Request().Body io.Reader` manually.
- func (ctx *context) UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) error {
- if ctx.request.Body == nil {
- return errors.New("unmarshal: empty body")
- }
- rawData, err := ioutil.ReadAll(ctx.request.Body)
- if err != nil {
- return err
- }
- if ctx.Application().ConfigurationReadOnly().GetDisableBodyConsumptionOnUnmarshal() {
- // * 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))
- }
- // check if the v contains its own decode
- // in this case the v should be a pointer also,
- // but this is up to the user's custom Decode implementation*
- //
- // See 'BodyDecoder' for more.
- if decoder, isDecoder := outPtr.(BodyDecoder); isDecoder {
- return decoder.Decode(rawData)
- }
- // // 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)
- // } <- no need for that, ReadJSON is documented enough to receive a pointer,
- // we don't need to reduce the performance here by using the reflect.TypeOf method.
- // f the v doesn't contains a self-body decoder use the custom unmarshaler to bind the body.
- return unmarshaler.Unmarshal(rawData, outPtr)
- }
- func (ctx *context) shouldOptimize() bool {
- return ctx.Application().ConfigurationReadOnly().GetEnableOptimizations()
- }
- // ReadJSON reads JSON from request's body and binds it to a value of any json-valid type.
- //
- // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-json/main.go
- func (ctx *context) ReadJSON(jsonObject interface{}) error {
- var unmarshaler = json.Unmarshal
- if ctx.shouldOptimize() {
- unmarshaler = jsoniter.Unmarshal
- }
- return ctx.UnmarshalBody(jsonObject, UnmarshalerFunc(unmarshaler))
- }
- // ReadXML reads XML from request's body and binds it to a value of any xml-valid type.
- //
- // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go
- func (ctx *context) ReadXML(xmlObject interface{}) error {
- return ctx.UnmarshalBody(xmlObject, UnmarshalerFunc(xml.Unmarshal))
- }
- var (
- errReadBody = errors.New("while trying to read %s from the request body. Trace %s")
- )
- // ReadForm binds the formObject with the form data
- // it supports any kind of struct.
- //
- // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-form/main.go
- func (ctx *context) ReadForm(formObject interface{}) error {
- values := ctx.FormValues()
- if values == nil {
- return errors.New("An empty form passed on ReadForm")
- }
- // or dec := formbinder.NewDecoder(&formbinder.DecoderOptions{TagName: "form"})
- // somewhere at the app level. I did change the tagName to "form"
- // inside its source code, so it's not needed for now.
- return errReadBody.With(formbinder.Decode(values, formObject))
- }
- // +------------------------------------------------------------+
- // | Body (raw) Writers |
- // +------------------------------------------------------------+
- // Write writes the data to the connection as part of an HTTP reply.
- //
- // If WriteHeader has not yet been called, Write calls
- // WriteHeader(http.StatusOK) before writing the data. If the Header
- // does not contain a Content-Type line, Write adds a Content-Type set
- // to the result of passing the initial 512 bytes of written data to
- // DetectContentType.
- //
- // Depending on the HTTP protocol version and the client, calling
- // Write or WriteHeader may prevent future reads on the
- // Request.Body. For HTTP/1.x requests, handlers should read any
- // needed request body data before writing the response. Once the
- // headers have been flushed (due to either an explicit Flusher.Flush
- // call or writing enough data to trigger a flush), the request body
- // may be unavailable. For HTTP/2 requests, the Go HTTP server permits
- // handlers to continue to read the request body while concurrently
- // writing the response. However, such behavior may not be supported
- // by all HTTP/2 clients. Handlers should read before writing if
- // possible to maximize compatibility.
- func (ctx *context) Write(rawBody []byte) (int, error) {
- return ctx.writer.Write(rawBody)
- }
- // Writef formats according to a format specifier and writes to the response.
- //
- // Returns the number of bytes written and any write error encountered.
- func (ctx *context) Writef(format string, a ...interface{}) (n int, err error) {
- return ctx.writer.Writef(format, a...)
- }
- // WriteString writes a simple string to the response.
- //
- // Returns the number of bytes written and any write error encountered.
- func (ctx *context) WriteString(body string) (n int, err error) {
- return ctx.writer.WriteString(body)
- }
- const (
- // ContentTypeHeaderKey is the header key of "Content-Type".
- ContentTypeHeaderKey = "Content-Type"
- // LastModifiedHeaderKey is the header key of "Last-Modified".
- LastModifiedHeaderKey = "Last-Modified"
- // IfModifiedSinceHeaderKey is the header key of "If-Modified-Since".
- IfModifiedSinceHeaderKey = "If-Modified-Since"
- // CacheControlHeaderKey is the header key of "Cache-Control".
- CacheControlHeaderKey = "Cache-Control"
- // ETagHeaderKey is the header key of "ETag".
- ETagHeaderKey = "ETag"
- // ContentDispositionHeaderKey is the header key of "Content-Disposition".
- ContentDispositionHeaderKey = "Content-Disposition"
- // ContentLengthHeaderKey is the header key of "Content-Length"
- ContentLengthHeaderKey = "Content-Length"
- // ContentEncodingHeaderKey is the header key of "Content-Encoding".
- ContentEncodingHeaderKey = "Content-Encoding"
- // GzipHeaderValue is the header value of "gzip".
- GzipHeaderValue = "gzip"
- // AcceptEncodingHeaderKey is the header key of "Accept-Encoding".
- AcceptEncodingHeaderKey = "Accept-Encoding"
- // VaryHeaderKey is the header key of "Vary".
- VaryHeaderKey = "Vary"
- )
- var unixEpochTime = time.Unix(0, 0)
- // IsZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
- func IsZeroTime(t time.Time) bool {
- return t.IsZero() || t.Equal(unixEpochTime)
- }
- // ParseTime parses a time header (such as the Date: header),
- // trying each forth formats (or three if Application's configuration's TimeFormat is defaulted)
- // that are allowed by HTTP/1.1:
- // Application's configuration's TimeFormat or/and http.TimeFormat,
- // time.RFC850, and time.ANSIC.
- //
- // Look `context#FormatTime` for the opossite operation (Time to string).
- var ParseTime = func(ctx Context, text string) (t time.Time, err error) {
- t, err = time.Parse(ctx.Application().ConfigurationReadOnly().GetTimeFormat(), text)
- if err != nil {
- return http.ParseTime(text)
- }
- return
- }
- // FormatTime returns a textual representation of the time value formatted
- // according to the Application's configuration's TimeFormat field
- // which defines the format.
- //
- // Look `context#ParseTime` for the opossite operation (string to Time).
- var FormatTime = func(ctx Context, t time.Time) string {
- return t.Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())
- }
- // SetLastModified sets the "Last-Modified" based on the "modtime" input.
- // If "modtime" is zero then it does nothing.
- //
- // It's mostly internally on core/router and context packages.
- func (ctx *context) SetLastModified(modtime time.Time) {
- if !IsZeroTime(modtime) {
- ctx.Header(LastModifiedHeaderKey, FormatTime(ctx, modtime.UTC())) // or modtime.UTC()?
- }
- }
- // CheckIfModifiedSince checks if the response is modified since the "modtime".
- // Note that it has nothing to do with server-side caching.
- // It does those checks by checking if the "If-Modified-Since" request header
- // sent by client or a previous server response header
- // (e.g with WriteWithExpiration or StaticEmbedded or Favicon etc.)
- // is a valid one and it's before the "modtime".
- //
- // A check for !modtime && err == nil is necessary to make sure that
- // it's not modified since, because it may return false but without even
- // had the chance to check the client-side (request) header due to some errors,
- // like the HTTP Method is not "GET" or "HEAD" or if the "modtime" is zero
- // or if parsing time from the header failed.
- //
- // It's mostly used internally, e.g. `context#WriteWithExpiration`.
- func (ctx *context) CheckIfModifiedSince(modtime time.Time) (bool, error) {
- if method := ctx.Method(); method != http.MethodGet && method != http.MethodHead {
- return false, errors.New("skip: method")
- }
- ims := ctx.GetHeader(IfModifiedSinceHeaderKey)
- if ims == "" || IsZeroTime(modtime) {
- return false, errors.New("skip: zero time")
- }
- t, err := ParseTime(ctx, ims)
- if err != nil {
- return false, errors.New("skip: " + err.Error())
- }
- // sub-second precision, so
- // use mtime < t+1s instead of mtime <= t to check for unmodified.
- if modtime.UTC().Before(t.Add(1 * time.Second)) {
- return false, nil
- }
- return true, nil
- }
- // WriteNotModified sends a 304 "Not Modified" status code to the client,
- // it makes sure that the content type, the content length headers
- // and any "ETag" are removed before the response sent.
- //
- // It's mostly used internally on core/router/fs.go and context methods.
- func (ctx *context) WriteNotModified() {
- // RFC 7232 section 4.1:
- // a sender SHOULD NOT generate representation metadata other than the
- // above listed fields unless said metadata exists for the purpose of
- // guiding cache updates (e.g.," Last-Modified" might be useful if the
- // response does not have an ETag field).
- h := ctx.ResponseWriter().Header()
- delete(h, ContentTypeHeaderKey)
- delete(h, ContentLengthHeaderKey)
- if h.Get(ETagHeaderKey) != "" {
- delete(h, LastModifiedHeaderKey)
- }
- ctx.StatusCode(http.StatusNotModified)
- }
- // WriteWithExpiration like Write but it sends with an expiration datetime
- // which is refreshed every package-level `StaticCacheDuration` field.
- func (ctx *context) WriteWithExpiration(body []byte, modtime time.Time) (int, error) {
- if modified, err := ctx.CheckIfModifiedSince(modtime); !modified && err == nil {
- ctx.WriteNotModified()
- return 0, nil
- }
- ctx.SetLastModified(modtime)
- return ctx.writer.Write(body)
- }
- // 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.writer
- notifyClosed := w.CloseNotify()
- for {
- select {
- // response writer forced to close, exit.
- case <-notifyClosed:
- return
- default:
- shouldContinue := writer(w)
- w.Flush()
- if !shouldContinue {
- return
- }
- }
- }
- }
- // +------------------------------------------------------------+
- // | Body Writers with compression |
- // +------------------------------------------------------------+
- // ClientSupportsGzip retruns true if the client supports gzip compression.
- func (ctx *context) ClientSupportsGzip() bool {
- if h := ctx.GetHeader(AcceptEncodingHeaderKey); h != "" {
- for _, v := range strings.Split(h, ";") {
- if strings.Contains(v, GzipHeaderValue) { // we do Contains because sometimes browsers has the q=, we don't use it atm. || strings.Contains(v,"deflate"){
- return true
- }
- }
- }
- return false
- }
- var (
- errClientDoesNotSupportGzip = errors.New("client doesn't supports gzip compression")
- )
- // 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)
- //
- // You may re-use this function in the same handler
- // to write more data many times without any troubles.
- func (ctx *context) WriteGzip(b []byte) (int, error) {
- if !ctx.ClientSupportsGzip() {
- return 0, errClientDoesNotSupportGzip
- }
- return ctx.GzipResponseWriter().Write(b)
- }
- // 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.writer.Write(b)
- }
- }
- return n, err
- }
- // GzipResponseWriter converts the current response writer into a response writer
- // which when its .Write called it compress the data to gzip and writes them to the client.
- //
- // Can be also disabled with its .Disable and .ResetBody to rollback to the usual response writer.
- func (ctx *context) GzipResponseWriter() *GzipResponseWriter {
- // if it's already a gzip response writer then just return it
- if gzipResWriter, ok := ctx.writer.(*GzipResponseWriter); ok {
- return gzipResWriter
- }
- // if it's not acquire a new from a pool
- // and register that as the ctx.ResponseWriter.
- gzipResWriter := AcquireGzipResponseWriter()
- gzipResWriter.BeginGzipResponse(ctx.writer)
- ctx.ResetResponseWriter(gzipResWriter)
- return gzipResWriter
- }
- // Gzip enables or disables (if enabled before) the gzip response writer,if the client
- // supports gzip compression, so the following response data will
- // be sent as compressed gzip data to the client.
- func (ctx *context) Gzip(enable bool) {
- if enable {
- if ctx.ClientSupportsGzip() {
- _ = ctx.GzipResponseWriter()
- }
- } else {
- if gzipResWriter, ok := ctx.writer.(*GzipResponseWriter); ok {
- gzipResWriter.Disable()
- }
- }
- }
- // +------------------------------------------------------------+
- // | Rich Body Content Writers/Renderers |
- // +------------------------------------------------------------+
- const (
- // NoLayout to disable layout for a particular template file
- NoLayout = "iris.nolayout"
- )
- // ViewLayout sets the "layout" option if and when .View
- // is being called afterwards, in the same request.
- // Useful when need to set or/and change a layout based on the previous handlers in the chain.
- //
- // Note that the 'layoutTmplFile' argument can be setted to iris.NoLayout || view.NoLayout || context.NoLayout
- // to disable the layout for a specific view render action,
- // it disables the engine's configuration's layout property.
- //
- // Look .ViewData and .View too.
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/view/context-view-data/
- func (ctx *context) ViewLayout(layoutTmplFile string) {
- ctx.values.Set(ctx.Application().ConfigurationReadOnly().GetViewLayoutContextKey(), layoutTmplFile)
- }
- // ViewData saves one or more key-value pair in order to be passed if and when .View
- // is being called afterwards, in the same request.
- // Useful when need to set or/and change template data from previous hanadlers in the chain.
- //
- // If .View's "binding" argument is not nil and it's not a type of map
- // then these data are being ignored, binding has the priority, so the main route's handler can still decide.
- // If binding is a map or context.Map then these data are being added to the view data
- // and passed to the template.
- //
- // After .View, the data are not destroyed, in order to be re-used if needed (again, in the same request as everything else),
- // to clear the view data, developers can call:
- // ctx.Set(ctx.Application().ConfigurationReadOnly().GetViewDataContextKey(), nil)
- //
- // If 'key' is empty then the value is added as it's (struct or map) and developer is unable to add other value.
- //
- // Look .ViewLayout and .View too.
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/view/context-view-data/
- func (ctx *context) ViewData(key string, value interface{}) {
- viewDataContextKey := ctx.Application().ConfigurationReadOnly().GetViewDataContextKey()
- if key == "" {
- ctx.values.Set(viewDataContextKey, value)
- return
- }
- v := ctx.values.Get(viewDataContextKey)
- if v == nil {
- ctx.values.Set(viewDataContextKey, Map{key: value})
- return
- }
- if data, ok := v.(map[string]interface{}); ok {
- data[key] = value
- } else if data, ok := v.(Map); ok {
- data[key] = value
- }
- }
- // GetViewData returns the values registered by `context#ViewData`.
- // The return value is `map[string]interface{}`, this means that
- // if a custom struct registered to ViewData then this function
- // will try to parse it to map, if failed then the return value is nil
- // A check for nil is always a good practise if different
- // kind of values or no data are registered via `ViewData`.
- //
- // Similarly to `viewData := ctx.Values().Get("iris.viewData")` or
- // `viewData := ctx.Values().Get(ctx.Application().ConfigurationReadOnly().GetViewDataContextKey())`.
- func (ctx *context) GetViewData() map[string]interface{} {
- viewDataContextKey := ctx.Application().ConfigurationReadOnly().GetViewDataContextKey()
- v := ctx.Values().Get(viewDataContextKey)
- // if no values found, then return nil
- if v == nil {
- return nil
- }
- // if struct, convert it to map[string]interface{}
- if structs.IsStruct(v) {
- return structs.Map(v)
- }
- // if pure map[string]interface{}
- if viewData, ok := v.(map[string]interface{}); ok {
- return viewData
- }
- // if context#Map
- if viewData, ok := v.(Map); ok {
- return viewData
- }
- // if failure, then return nil
- return nil
- }
- // View renders a template based on the registered view engine(s).
- // First argument accepts the filename, relative to the view engine's Directory and Extension,
- // i.e: if directory is "./templates" and want to render the "./templates/users/index.html"
- // then you pass the "users/index.html" as the filename argument.
- //
- // The second optional argument can receive a single "view model"
- // that will be binded to the view template if it's not nil,
- // otherwise it will check for previous view data stored by the `ViewData`
- // even if stored at any previous handler(middleware) for the same request.
- //
- // Look .ViewData and .ViewLayout too.
- //
- // Examples: https://github.com/kataras/iris/tree/master/_examples/view
- func (ctx *context) View(filename string, optionalViewModel ...interface{}) error {
- ctx.ContentType(ContentHTMLHeaderValue)
- cfg := ctx.Application().ConfigurationReadOnly()
- layout := ctx.values.GetString(cfg.GetViewLayoutContextKey())
- var bindingData interface{}
- if len(optionalViewModel) > 0 {
- // a nil can override the existing data or model sent by `ViewData`.
- bindingData = optionalViewModel[0]
- } else {
- bindingData = ctx.values.Get(cfg.GetViewDataContextKey())
- }
- err := ctx.Application().View(ctx.writer, filename, layout, bindingData)
- if err != nil {
- ctx.StatusCode(http.StatusInternalServerError)
- ctx.StopExecution()
- }
- return err
- }
- const (
- // ContentBinaryHeaderValue header value for binary data.
- ContentBinaryHeaderValue = "application/octet-stream"
- // ContentHTMLHeaderValue is the string of text/html response header's content type value.
- ContentHTMLHeaderValue = "text/html"
- // ContentJSONHeaderValue header value for JSON data.
- ContentJSONHeaderValue = "application/json"
- // ContentJavascriptHeaderValue header value for JSONP & Javascript data.
- ContentJavascriptHeaderValue = "application/javascript"
- // ContentTextHeaderValue header value for Text data.
- ContentTextHeaderValue = "text/plain"
- // ContentXMLHeaderValue header value for XML data.
- ContentXMLHeaderValue = "text/xml"
- // ContentMarkdownHeaderValue custom key/content type, the real is the text/html.
- ContentMarkdownHeaderValue = "text/markdown"
- // ContentYAMLHeaderValue header value for YAML data.
- ContentYAMLHeaderValue = "application/x-yaml"
- )
- // Binary writes out the raw bytes as binary data.
- func (ctx *context) Binary(data []byte) (int, error) {
- ctx.ContentType(ContentBinaryHeaderValue)
- return ctx.Write(data)
- }
- // Text writes out a string as plain text.
- func (ctx *context) Text(text string) (int, error) {
- ctx.ContentType(ContentTextHeaderValue)
- return ctx.writer.WriteString(text)
- }
- // HTML writes out a string as text/html.
- func (ctx *context) HTML(htmlContents string) (int, error) {
- ctx.ContentType(ContentHTMLHeaderValue)
- return ctx.writer.WriteString(htmlContents)
- }
- // JSON contains the options for the JSON (Context's) Renderer.
- type JSON struct {
- // http-specific
- StreamingJSON bool
- // content-specific
- UnescapeHTML bool
- Indent string
- Prefix string
- }
- // JSONP contains the options for the JSONP (Context's) Renderer.
- type JSONP struct {
- // content-specific
- Indent string
- Callback string
- }
- // XML contains the options for the XML (Context's) Renderer.
- type XML struct {
- // content-specific
- Indent string
- Prefix string
- }
- // Markdown contains the options for the Markdown (Context's) Renderer.
- type Markdown struct {
- // content-specific
- Sanitize bool
- }
- var (
- newLineB = []byte("\n")
- // the html codes for unescaping
- ltHex = []byte("\\u003c")
- lt = []byte("<")
- gtHex = []byte("\\u003e")
- gt = []byte(">")
- andHex = []byte("\\u0026")
- and = []byte("&")
- )
- // WriteJSON marshals the given interface object and writes the JSON response to the 'writer'.
- // Ignores StatusCode, Gzip, StreamingJSON options.
- func WriteJSON(writer io.Writer, v interface{}, options JSON, enableOptimization ...bool) (int, error) {
- var (
- result []byte
- err error
- optimize = len(enableOptimization) > 0 && enableOptimization[0]
- )
- if indent := options.Indent; indent != "" {
- marshalIndent := json.MarshalIndent
- if optimize {
- marshalIndent = jsoniter.ConfigCompatibleWithStandardLibrary.MarshalIndent
- }
- result, err = marshalIndent(v, "", indent)
- result = append(result, newLineB...)
- } else {
- marshal := json.Marshal
- if optimize {
- marshal = jsoniter.ConfigCompatibleWithStandardLibrary.Marshal
- }
- result, err = marshal(v)
- }
- if err != nil {
- return 0, err
- }
- if options.UnescapeHTML {
- result = bytes.Replace(result, ltHex, lt, -1)
- result = bytes.Replace(result, gtHex, gt, -1)
- result = bytes.Replace(result, andHex, and, -1)
- }
- if prefix := options.Prefix; prefix != "" {
- result = append([]byte(prefix), result...)
- }
- return writer.Write(result)
- }
- // DefaultJSONOptions is the optional settings that are being used
- // inside `ctx.JSON`.
- var DefaultJSONOptions = JSON{}
- // JSON marshals the given interface object and writes the JSON response to the client.
- func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) {
- options := DefaultJSONOptions
- if len(opts) > 0 {
- options = opts[0]
- }
- optimize := ctx.shouldOptimize()
- ctx.ContentType(ContentJSONHeaderValue)
- if options.StreamingJSON {
- if optimize {
- var jsoniterConfig = jsoniter.Config{
- EscapeHTML: !options.UnescapeHTML,
- IndentionStep: 4,
- }.Froze()
- enc := jsoniterConfig.NewEncoder(ctx.writer)
- err = enc.Encode(v)
- } else {
- enc := json.NewEncoder(ctx.writer)
- enc.SetEscapeHTML(!options.UnescapeHTML)
- enc.SetIndent(options.Prefix, options.Indent)
- err = enc.Encode(v)
- }
- if err != nil {
- ctx.StatusCode(http.StatusInternalServerError) // it handles the fallback to normal mode here which also removes the gzip headers.
- return 0, err
- }
- return ctx.writer.Written(), err
- }
- n, err = WriteJSON(ctx.writer, v, options, optimize)
- if err != nil {
- ctx.StatusCode(http.StatusInternalServerError)
- return 0, err
- }
- return n, err
- }
- var (
- finishCallbackB = []byte(");")
- )
- // WriteJSONP marshals the given interface object and writes the JSON response to the writer.
- func WriteJSONP(writer io.Writer, v interface{}, options JSONP, enableOptimization ...bool) (int, error) {
- if callback := options.Callback; callback != "" {
- writer.Write([]byte(callback + "("))
- defer writer.Write(finishCallbackB)
- }
- optimize := len(enableOptimization) > 0 && enableOptimization[0]
- if indent := options.Indent; indent != "" {
- marshalIndent := json.MarshalIndent
- if optimize {
- marshalIndent = jsoniter.ConfigCompatibleWithStandardLibrary.MarshalIndent
- }
- result, err := marshalIndent(v, "", indent)
- if err != nil {
- return 0, err
- }
- result = append(result, newLineB...)
- return writer.Write(result)
- }
- marshal := json.Marshal
- if optimize {
- marshal = jsoniter.ConfigCompatibleWithStandardLibrary.Marshal
- }
- result, err := marshal(v)
- if err != nil {
- return 0, err
- }
- return writer.Write(result)
- }
- // DefaultJSONPOptions is the optional settings that are being used
- // inside `ctx.JSONP`.
- var DefaultJSONPOptions = JSONP{}
- // JSONP marshals the given interface object and writes the JSON response to the client.
- func (ctx *context) JSONP(v interface{}, opts ...JSONP) (int, error) {
- options := DefaultJSONPOptions
- if len(opts) > 0 {
- options = opts[0]
- }
- ctx.ContentType(ContentJavascriptHeaderValue)
- n, err := WriteJSONP(ctx.writer, v, options, ctx.shouldOptimize())
- if err != nil {
- ctx.StatusCode(http.StatusInternalServerError)
- return 0, err
- }
- return n, err
- }
- // WriteXML marshals the given interface object and writes the XML response to the writer.
- func WriteXML(writer io.Writer, v interface{}, options XML) (int, error) {
- if prefix := options.Prefix; prefix != "" {
- writer.Write([]byte(prefix))
- }
- if indent := options.Indent; indent != "" {
- result, err := xml.MarshalIndent(v, "", indent)
- if err != nil {
- return 0, err
- }
- result = append(result, newLineB...)
- return writer.Write(result)
- }
- result, err := xml.Marshal(v)
- if err != nil {
- return 0, err
- }
- return writer.Write(result)
- }
- // DefaultXMLOptions is the optional settings that are being used
- // from `ctx.XML`.
- var DefaultXMLOptions = XML{}
- // XML marshals the given interface object and writes the XML response to the client.
- func (ctx *context) XML(v interface{}, opts ...XML) (int, error) {
- options := DefaultXMLOptions
- if len(opts) > 0 {
- options = opts[0]
- }
- ctx.ContentType(ContentXMLHeaderValue)
- n, err := WriteXML(ctx.writer, v, options)
- if err != nil {
- ctx.StatusCode(http.StatusInternalServerError)
- return 0, err
- }
- return n, err
- }
- // WriteMarkdown parses the markdown to html and writes these contents to the writer.
- func WriteMarkdown(writer io.Writer, markdownB []byte, options Markdown) (int, error) {
- buf := blackfriday.Run(markdownB)
- if options.Sanitize {
- buf = bluemonday.UGCPolicy().SanitizeBytes(buf)
- }
- return writer.Write(buf)
- }
- // DefaultMarkdownOptions is the optional settings that are being used
- // from `WriteMarkdown` and `ctx.Markdown`.
- var DefaultMarkdownOptions = Markdown{}
- // Markdown parses the markdown to html and renders its result to the client.
- func (ctx *context) Markdown(markdownB []byte, opts ...Markdown) (int, error) {
- options := DefaultMarkdownOptions
- if len(opts) > 0 {
- options = opts[0]
- }
- ctx.ContentType(ContentHTMLHeaderValue)
- n, err := WriteMarkdown(ctx.writer, markdownB, options)
- if err != nil {
- ctx.StatusCode(http.StatusInternalServerError)
- return 0, err
- }
- return n, err
- }
- // YAML marshals the "v" using the yaml marshaler and renders its result to the client.
- func (ctx *context) YAML(v interface{}) (int, error) {
- out, err := yaml.Marshal(v)
- if err != nil {
- ctx.StatusCode(http.StatusInternalServerError)
- return 0, err
- }
- ctx.ContentType(ContentYAMLHeaderValue)
- return ctx.Write(out)
- }
- // +------------------------------------------------------------+
- // | Serve files |
- // +------------------------------------------------------------+
- var (
- errServeContent = errors.New("while trying to serve content to the client. Trace %s")
- )
- // ServeContent serves content, headers are autoset
- // receives three parameters, it's low-level function, instead you can use .ServeFile(string,bool)/SendFile(string,string)
- //
- // You can define your own "Content-Type" header also, after this function call
- // Doesn't implements resuming (by range), use ctx.SendFile instead
- func (ctx *context) ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error {
- if modified, err := ctx.CheckIfModifiedSince(modtime); !modified && err == nil {
- ctx.WriteNotModified()
- return nil
- }
- ctx.ContentType(filename)
- ctx.SetLastModified(modtime)
- var out io.Writer
- if gzipCompression && ctx.ClientSupportsGzip() {
- AddGzipHeaders(ctx.writer)
- gzipWriter := acquireGzipWriter(ctx.writer)
- defer releaseGzipWriter(gzipWriter)
- out = gzipWriter
- } else {
- out = ctx.writer
- }
- _, err := io.Copy(out, content)
- return errServeContent.With(err) ///TODO: add an int64 as return value for the content length written like other writers or let it as it's in order to keep the stable api?
- }
- // ServeFile serves a view file, to send a file ( zip for example) to the client you should use the SendFile(serverfilename,clientfilename)
- // receives two parameters
- // filename/path (string)
- // gzipCompression (bool)
- //
- // You can define your own "Content-Type" header also, after this function call
- // This function doesn't implement resuming (by range), use ctx.SendFile instead
- //
- // Use it when you want to serve css/js/... files to the client, for bigger files and 'force-download' use the SendFile.
- 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() {
- return ctx.ServeFile(path.Join(filename, "index.html"), gzipCompression)
- }
- return ctx.ServeContent(f, fi.Name(), fi.ModTime(), gzipCompression)
- }
- // SendFile sends file for force-download to the client
- //
- // Use this instead of ServeFile to 'force-download' bigger files to the client.
- func (ctx *context) SendFile(filename string, destinationName string) error {
- ctx.writer.Header().Set(ContentDispositionHeaderKey, "attachment;filename="+destinationName)
- return ctx.ServeFile(filename, false)
- }
- // +------------------------------------------------------------+
- // | Cookies |
- // +------------------------------------------------------------+
- // CookieOption is the type of function that is accepted on
- // context's methods like `SetCookieKV`, `RemoveCookie` and `SetCookie`
- // as their (last) variadic input argument to amend the end cookie's form.
- //
- // Any custom or built'n `CookieOption` is valid,
- // see `CookiePath`, `CookieCleanPath`, `CookieExpires` and `CookieHTTPOnly` for more.
- type CookieOption func(*http.Cookie)
- // CookiePath is a `CookieOption`.
- // Use it to change the cookie's Path field.
- func CookiePath(path string) CookieOption {
- return func(c *http.Cookie) {
- c.Path = path
- }
- }
- // CookieCleanPath is a `CookieOption`.
- // Use it to clear the cookie's Path field, exactly the same as `CookiePath("")`.
- func CookieCleanPath(c *http.Cookie) {
- c.Path = ""
- }
- // CookieExpires is a `CookieOption`.
- // Use it to change the cookie's Expires and MaxAge fields by passing the lifetime of the cookie.
- func CookieExpires(durFromNow time.Duration) CookieOption {
- return func(c *http.Cookie) {
- c.Expires = time.Now().Add(durFromNow)
- c.MaxAge = int(durFromNow.Seconds())
- }
- }
- // CookieHTTPOnly is a `CookieOption`.
- // Use it to set the cookie's HttpOnly field to false or true.
- // HttpOnly field defaults to true for `RemoveCookie` and `SetCookieKV`.
- func CookieHTTPOnly(httpOnly bool) CookieOption {
- return func(c *http.Cookie) {
- c.HttpOnly = httpOnly
- }
- }
- type (
- // CookieEncoder should encode the cookie value.
- // Should accept as first argument the cookie name
- // and as second argument the cookie value ptr.
- // Should return an encoded value or an empty one if encode operation failed.
- // Should return an error if encode operation failed.
- //
- // Note: Errors are not printed, so you have to know what you're doing,
- // and remember: if you use AES it only supports key sizes of 16, 24 or 32 bytes.
- // You either need to provide exactly that amount or you derive the key from what you type in.
- //
- // See `CookieDecoder` too.
- CookieEncoder func(cookieName string, value interface{}) (string, error)
- // CookieDecoder should decode the cookie value.
- // Should accept as first argument the cookie name,
- // as second argument the encoded cookie value and as third argument the decoded value ptr.
- // Should return a decoded value or an empty one if decode operation failed.
- // Should return an error if decode operation failed.
- //
- // Note: Errors are not printed, so you have to know what you're doing,
- // and remember: if you use AES it only supports key sizes of 16, 24 or 32 bytes.
- // You either need to provide exactly that amount or you derive the key from what you type in.
- //
- // See `CookieEncoder` too.
- CookieDecoder func(cookieName string, cookieValue string, v interface{}) error
- )
- // CookieEncode is a `CookieOption`.
- // Provides encoding functionality when adding a cookie.
- // Accepts a `CookieEncoder` and sets the cookie's value to the encoded value.
- // Users of that is the `SetCookie` and `SetCookieKV`.
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/securecookie
- func CookieEncode(encode CookieEncoder) CookieOption {
- return func(c *http.Cookie) {
- newVal, err := encode(c.Name, c.Value)
- if err != nil {
- c.Value = ""
- } else {
- c.Value = newVal
- }
- }
- }
- // CookieDecode is a `CookieOption`.
- // Provides decoding functionality when retrieving a cookie.
- // Accepts a `CookieDecoder` and sets the cookie's value to the decoded value before return by the `GetCookie`.
- // User of that is the `GetCookie`.
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/securecookie
- func CookieDecode(decode CookieDecoder) CookieOption {
- return func(c *http.Cookie) {
- if err := decode(c.Name, c.Value, &c.Value); err != nil {
- c.Value = ""
- }
- }
- }
- // SetCookie adds a cookie.
- // Use of the "options" is not required, they can be used to amend the "cookie".
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
- func (ctx *context) SetCookie(cookie *http.Cookie, options ...CookieOption) {
- for _, opt := range options {
- opt(cookie)
- }
- http.SetCookie(ctx.writer, cookie)
- }
- // SetCookieKV adds a cookie, requires the name(string) and the value(string).
- //
- // By default it expires at 2 hours and it's added to the root path,
- // use the `CookieExpires` and `CookiePath` to modify them.
- // Alternatively: ctx.SetCookie(&http.Cookie{...})
- //
- // If you want to set custom the path:
- // ctx.SetCookieKV(name, value, iris.CookiePath("/custom/path/cookie/will/be/stored"))
- //
- // If you want to be visible only to current request path:
- // (note that client should be responsible for that if server sent an empty cookie's path, all browsers are compatible)
- // ctx.SetCookieKV(name, value, iris.CookieCleanPath/iris.CookiePath(""))
- // More:
- // iris.CookieExpires(time.Duration)
- // iris.CookieHTTPOnly(false)
- //
- // Examples: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
- func (ctx *context) SetCookieKV(name, value string, options ...CookieOption) {
- c := &http.Cookie{}
- c.Path = "/"
- c.Name = name
- c.Value = url.QueryEscape(value)
- c.HttpOnly = true
- c.Expires = time.Now().Add(SetCookieKVExpiration)
- c.MaxAge = int(SetCookieKVExpiration.Seconds())
- ctx.SetCookie(c, options...)
- }
- // GetCookie returns cookie's value by it's name
- // returns empty string if nothing was found.
- //
- // If you want more than the value then:
- // cookie, err := ctx.Request().Cookie("name")
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
- func (ctx *context) GetCookie(name string, options ...CookieOption) string {
- cookie, err := ctx.request.Cookie(name)
- if err != nil {
- return ""
- }
- for _, opt := range options {
- opt(cookie)
- }
- value, _ := url.QueryUnescape(cookie.Value)
- return value
- }
- // SetCookieKVExpiration is 2 hours by-default
- // you can change it or simple, use the SetCookie for more control.
- //
- // See `SetCookieKVExpiration` and `CookieExpires` for more.
- var SetCookieKVExpiration = time.Duration(120) * time.Minute
- // RemoveCookie deletes a cookie by it's name and path = "/".
- // Tip: change the cookie's path to the current one by: RemoveCookie("name", iris.CookieCleanPath)
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
- func (ctx *context) RemoveCookie(name string, options ...CookieOption) {
- c := &http.Cookie{}
- c.Name = name
- c.Value = ""
- c.Path = "/" // if user wants to change it, use of the CookieOption `CookiePath` is required if not `ctx.SetCookie`.
- c.HttpOnly = true
- // RFC says 1 second, but let's do it 1 to make sure is working
- exp := time.Now().Add(-time.Duration(1) * time.Minute)
- c.Expires = exp
- c.MaxAge = -1
- ctx.SetCookie(c, options...)
- // delete request's cookie also, which is temporary available.
- ctx.request.Header.Set("Cookie", "")
- }
- // VisitAllCookies takes a visitor which loops
- // on each (request's) cookies' name and value.
- func (ctx *context) VisitAllCookies(visitor func(name string, value string)) {
- for _, cookie := range ctx.request.Cookies() {
- visitor(cookie.Name, cookie.Value)
- }
- }
- 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.GetHeader(CacheControlHeaderKey)
- 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
- }
- // +------------------------------------------------------------+
- // | Advanced: Response Recorder and Transactions |
- // +------------------------------------------------------------+
- // 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.writer.(*responseWriter); ok {
- recorder := AcquireResponseRecorder()
- recorder.BeginRecord(w)
- ctx.ResetResponseWriter(recorder)
- }
- }
- // 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.writer.(*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.writer.(*ResponseRecorder)
- return rr, ok
- }
- // non-detailed error log for transacton unexpected panic
- var errTransactionInterrupted = errors.New("transaction interrupted, recovery from panic:\n%s")
- // BeginTransaction starts a scoped transaction.
- //
- // Can't say a lot here because it will take more than 200 lines to write about.
- // You can search third-party articles or books on how Business Transaction works (it's quite simple, especially here).
- //
- // 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.
- // Transactions have their own middleware ecosystem also.
- //
- // See https://github.com/kataras/iris/tree/master/_examples/ for more
- func (ctx *context) BeginTransaction(pipe func(t *Transaction)) {
- // do NOT begin a transaction when the previous transaction has been failed
- // and it was requested scoped or SkipTransactions called manually.
- if ctx.TransactionsSkipped() {
- return
- }
- // start recording in order to be able to control the full response writer
- ctx.Record()
- t := newTransaction(ctx) // it calls this *context, so the overriding with a new pool's New of context.Context wil not work here.
- defer func() {
- if err := recover(); err != nil {
- ctx.Application().Logger().Warn(errTransactionInterrupted.Format(err).Error())
- // complete (again or not , doesn't matters) the scope without loud
- t.Complete(nil)
- // we continue as normal, no need to return here*
- }
- // write the temp contents to the original writer
- t.Context().ResponseWriter().WriteTo(ctx.writer)
- // 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.FireStatusCode to work inside transactions
- t.Context().ResetResponseWriter(ctx.writer)
- }()
- // run the worker with its context clone inside.
- pipe(t)
- }
- // 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()
- const skipTransactionsContextKey = "@transictions_skipped"
- // 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.values.Set(skipTransactionsContextKey, 1)
- }
- // TransactionsSkipped returns true if the transactions skipped or canceled at all.
- func (ctx *context) TransactionsSkipped() bool {
- if n, err := ctx.values.GetInt(skipTransactionsContextKey); err == nil && n == 1 {
- return true
- }
- return false
- }
- // Exec calls the framewrok's ServeCtx
- // based on this context but with a changed method and path
- // 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.
- //
- // You can find the Route by app.GetRoute("theRouteName")
- // you can set a route name as: myRoute := app.Get("/mypath", handler)("theRouteName")
- // that will set a name to the route and returns its RouteInfo instance for further usage.
- //
- // It doesn't changes the global state, if a route was "offline" it remains offline.
- //
- // app.None(...) and app.GetRoutes().Offline(route)/.Online(route, method)
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/routing/route-state
- //
- // User can get the response by simple using rec := ctx.Recorder(); rec.Body()/rec.StatusCode()/rec.Header().
- //
- // context's Values and the Session are kept in order to be able to communicate via the result route.
- //
- // It's for extreme use cases, 99% of the times will never be useful for you.
- func (ctx *context) Exec(method string, path string) {
- if path == "" {
- return
- }
- if method == "" {
- method = "GET"
- }
- // backup the handlers
- backupHandlers := ctx.Handlers()[0:]
- backupPos := ctx.HandlerIndex(-1)
- // backup the request path information
- backupPath := ctx.Path()
- backupMethod := ctx.Method()
- // don't backupValues := ctx.Values().ReadOnly()
- // [values stays]
- // reset handlers
- ctx.SetHandlers(nil)
- req := ctx.Request()
- // set the request to be align with the 'againstRequestPath'
- req.RequestURI = path
- req.URL.Path = path
- req.Method = method
- // execute the route from the (internal) context router
- // this way we keep the sessions and the values
- ctx.Application().ServeHTTPC(ctx)
- // set back the old handlers and the last known index
- ctx.SetHandlers(backupHandlers)
- ctx.HandlerIndex(backupPos)
- // set the request back to its previous state
- req.RequestURI = backupPath
- req.URL.Path = backupPath
- req.Method = backupMethod
- // don't fill the values in order to be able to communicate from and to.
- // // fill the values as they were before
- // backupValues.Visit(func(key string, value interface{}) {
- // ctx.Values().Set(key, value)
- // })
- }
- // RouteExists reports whether a particular route exists
- // It will search from the current subdomain of context's host, if not inside the root domain.
- func (ctx *context) RouteExists(method, path string) bool {
- return ctx.Application().RouteExists(ctx, method, path)
- }
- // Application returns the iris app instance which belongs to this context.
- // Worth to notice that this function returns an interface
- // of the Application, which contains methods that are safe
- // to be executed at serve-time. The full app's fields
- // and methods are not available here for the developer's safety.
- func (ctx *context) Application() Application {
- return ctx.app
- }
- var lastCapturedContextID uint64
- // LastCapturedContextID returns the total number of `context#String` calls.
- func LastCapturedContextID() uint64 {
- return atomic.LoadUint64(&lastCapturedContextID)
- }
- // String returns the string representation of this request.
- // Each context has a unique string representation.
- // It can be used for simple debugging scenarios, i.e print context as string.
- //
- // What it returns? A number which declares the length of the
- // total `String` calls per executable application, followed
- // by the remote IP (the client) and finally the method:url.
- func (ctx *context) String() string {
- if ctx.id == 0 {
- // set the id here.
- forward := atomic.AddUint64(&lastCapturedContextID, 1)
- ctx.id = forward
- }
- return fmt.Sprintf("[%d] %s ▶ %s:%s",
- ctx.id, ctx.RemoteAddr(), ctx.Method(), ctx.Request().RequestURI)
- }
|