diff --git a/HISTORY.md b/HISTORY.md index 7969a2843d..69b8d7124b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -371,6 +371,8 @@ Other Improvements: ![DBUG routes](https://iris-go.com/images/v12.2.0-dbug2.png?v=0) +- Fixed handler's error response not be respected when response recorder or gzip writer was used instead of the common writer. Fixes [#1531](https://github.com/kataras/iris/issues/1531). It contains a **BREAKING CHANGE** of: the new `Configuration.ResetOnFireErrorCode` field should be set **to true** in order to behave as it used before this update (to reset the contents on recorder or gzip writer). + - New builtin [requestid](https://github.com/kataras/iris/tree/master/middleware/requestid) middleware. - New builtin [JWT](https://github.com/kataras/iris/tree/master/middleware/jwt) middleware based on [square/go-jose](https://github.com/square/go-jose) featured with optional encryption to set claims with sensitive data when necessary. diff --git a/_examples/README.md b/_examples/README.md index f96e842073..14c84c9571 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -51,7 +51,7 @@ * [Route State](routing/route-state/main.go) * [Reverse Routing](routing/reverse/main.go) * [Router Wrapper](routing/custom-wrapper/main.go) - * [Custom Router](routing/custom-high-level-router/main.go) + * [Custom Router](routing/custom-router/main.go) * Custom Context * [Method Overriding](routing/custom-context/method-overriding/main.go) * [New Implementation](routing/custom-context/new-implementation/main.go) diff --git a/_examples/bootstrap/bootstrap/bootstrapper.go b/_examples/bootstrap/bootstrap/bootstrapper.go index 47bbf5e145..7bbbd3edc9 100644 --- a/_examples/bootstrap/bootstrap/bootstrapper.go +++ b/_examples/bootstrap/bootstrap/bootstrapper.go @@ -61,7 +61,7 @@ func (b *Bootstrapper) SetupWebsockets(endpoint string, handler websocket.ConnHa } // SetupErrorHandlers prepares the http error handlers -// `(context.StatusCodeNotSuccessful`, which defaults to < 200 || >= 400 but you can change it). +// `(context.StatusCodeNotSuccessful`, which defaults to >=400 (but you can change it). func (b *Bootstrapper) SetupErrorHandlers() { b.OnAnyErrorCode(func(ctx iris.Context) { err := iris.Map{ diff --git a/_examples/routing/custom-high-level-router/main.go b/_examples/routing/custom-router/main.go similarity index 98% rename from _examples/routing/custom-high-level-router/main.go rename to _examples/routing/custom-router/main.go index 5d9e8e4be0..d7d20eb927 100644 --- a/_examples/routing/custom-high-level-router/main.go +++ b/_examples/routing/custom-router/main.go @@ -72,7 +72,7 @@ func (r *customRouter) RouteExists(ctx iris.Context, method, path string) bool { return false } -func (r *customRouter) FireErrorCode(ctx iris.Context) { +func (r *customRouter) FireErrorCode(ctx iris.Context, reset bool) { // responseStatusCode := ctx.GetStatusCode() // set by prior ctx.StatusCode calls // [...] } diff --git a/_examples/view/herotemplate/app.go b/_examples/view/herotemplate/app.go index c95715c2ac..3765f65e0f 100644 --- a/_examples/view/herotemplate/app.go +++ b/_examples/view/herotemplate/app.go @@ -3,7 +3,7 @@ package main import ( "bytes" - "github.com/kataras/iris/v12/_examples/response-writer/herotemplate/template" + "github.com/kataras/iris/v12/_examples/view/herotemplate/template" "github.com/kataras/iris/v12" ) diff --git a/_examples/view/quicktemplate/controllers/execute_template.go b/_examples/view/quicktemplate/controllers/execute_template.go index 11a840059d..02eb90052a 100644 --- a/_examples/view/quicktemplate/controllers/execute_template.go +++ b/_examples/view/quicktemplate/controllers/execute_template.go @@ -1,7 +1,7 @@ package controllers import ( - "github.com/kataras/iris/v12/_examples/response-writer/quicktemplate/templates" + "github.com/kataras/iris/v12/_examples/view/quicktemplate/templates" "github.com/kataras/iris/v12" ) diff --git a/_examples/view/quicktemplate/controllers/hello.go b/_examples/view/quicktemplate/controllers/hello.go index 6caaa98991..009742a406 100644 --- a/_examples/view/quicktemplate/controllers/hello.go +++ b/_examples/view/quicktemplate/controllers/hello.go @@ -1,7 +1,7 @@ package controllers import ( - "github.com/kataras/iris/v12/_examples/response-writer/quicktemplate/templates" + "github.com/kataras/iris/v12/_examples/view/quicktemplate/templates" "github.com/kataras/iris/v12" ) diff --git a/_examples/view/quicktemplate/controllers/index.go b/_examples/view/quicktemplate/controllers/index.go index a0b679c040..56cf6717ea 100644 --- a/_examples/view/quicktemplate/controllers/index.go +++ b/_examples/view/quicktemplate/controllers/index.go @@ -1,7 +1,7 @@ package controllers import ( - "github.com/kataras/iris/v12/_examples/response-writer/quicktemplate/templates" + "github.com/kataras/iris/v12/_examples/view/quicktemplate/templates" "github.com/kataras/iris/v12" ) diff --git a/_examples/view/quicktemplate/main.go b/_examples/view/quicktemplate/main.go index ae19c55c20..7a63ab0d4c 100644 --- a/_examples/view/quicktemplate/main.go +++ b/_examples/view/quicktemplate/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/kataras/iris/v12/_examples/response-writer/quicktemplate/controllers" + "github.com/kataras/iris/v12/_examples/view/quicktemplate/controllers" "github.com/kataras/iris/v12" ) diff --git a/configuration.go b/configuration.go index 97fa48c2ea..b8a436674a 100644 --- a/configuration.go +++ b/configuration.go @@ -269,13 +269,6 @@ var WithEmptyFormError = func(app *Application) { app.config.FireEmptyFormError = true } -// WithoutAutoFireStatusCode disables the AutoFireStatusCode setting. -// -// See `Configuration`. -var WithoutAutoFireStatusCode = func(app *Application) { - app.config.DisableAutoFireStatusCode = true -} - // WithPathEscape sets the EnablePathEscape setting to true. // // See `Configuration`. @@ -305,6 +298,20 @@ var WithFireMethodNotAllowed = func(app *Application) { app.config.FireMethodNotAllowed = true } +// WithoutAutoFireStatusCode sets the DisableAutoFireStatusCode setting to true. +// +// See `Configuration`. +var WithoutAutoFireStatusCode = func(app *Application) { + app.config.DisableAutoFireStatusCode = true +} + +// WithResetOnFireErrorCode sets the ResetOnFireErrorCode setting to true. +// +// See `Configuration`. +var WithResetOnFireErrorCode = func(app *Application) { + app.config.ResetOnFireErrorCode = true +} + // WithTimeFormat sets the TimeFormat setting. // // See `Configuration`. @@ -829,6 +836,21 @@ type Configuration struct { // fires the 405 error instead of 404 // Defaults to false. FireMethodNotAllowed bool `json:"fireMethodNotAllowed,omitempty" yaml:"FireMethodNotAllowed" toml:"FireMethodNotAllowed"` + // DisableAutoFireStatusCode if true then it turns off the http error status code + // handler automatic execution on error code from a `Context.StatusCode` call. + // By-default a custom http error handler will be fired when "Context.StatusCode(errorCode)" called. + // + // Defaults to false. + DisableAutoFireStatusCode bool `json:"disableAutoFireStatusCode,omitempty" yaml:"DisableAutoFireStatusCode" toml:"DisableAutoFireStatusCode"` + // ResetOnFireErrorCode if true then any previously response body or headers through + // response recorder or gzip writer will be ignored and the router + // will fire the registered (or default) HTTP error handler instead. + // See `core/router/handler#FireErrorCode` and `Context.EndRequest` for more details. + // + // Read more at: https://github.com/kataras/iris/issues/1531 + // + // Defaults to false. + ResetOnFireErrorCode bool `json:"resetOnFireErrorCode,omitempty" yaml:"ResetOnFireErrorCode" toml:"ResetOnFireErrorCode"` // EnableOptimization when this field is true // then the application tries to optimize for the best performance where is possible. @@ -848,20 +870,6 @@ type Configuration struct { // will return an `iris.ErrEmptyForm` on empty request form data. FireEmptyFormError bool `json:"fireEmptyFormError,omitempty" yaml:"FireEmptyFormError" yaml:"FireEmptyFormError"` - // DisableAutoFireStatusCode if true then it turns off the http error status code handler automatic execution - // from (`context.StatusCodeNotSuccessful`, defaults to < 200 || >= 400). - // If that is false then for a direct error firing, then call the "context#FireStatusCode(statusCode)" manually. - // - // By-default a custom http error handler will be fired when "context.StatusCode(code)" called, - // code should be equal with the result of the the `context.StatusCodeNotSuccessful` in order to be received as an "http error handler". - // - // Developer may want this option to set as true in order to manually call the - // error handlers when needed via "context#FireStatusCode(< 200 || >= 400)". - // HTTP Custom error handlers are being registered via app.OnErrorCode(code, handler)". - // - // Defaults to false. - DisableAutoFireStatusCode bool `json:"disableAutoFireStatusCode,omitempty" yaml:"DisableAutoFireStatusCode" toml:"DisableAutoFireStatusCode"` - // TimeFormat time format for any kind of datetime parsing // Defaults to "Mon, 02 Jan 2006 15:04:05 GMT". TimeFormat string `json:"timeFormat,omitempty" yaml:"TimeFormat" toml:"TimeFormat"` @@ -1038,19 +1046,28 @@ func (c Configuration) GetFireEmptyFormError() bool { return c.DisableBodyConsumptionOnUnmarshal } -// GetDisableAutoFireStatusCode returns the Configuration#DisableAutoFireStatusCode. +// GetDisableAutoFireStatusCode returns the Configuration.DisableAutoFireStatusCode. // Returns true when the http error status code handler automatic execution turned off. func (c Configuration) GetDisableAutoFireStatusCode() bool { return c.DisableAutoFireStatusCode } -// GetTimeFormat returns the Configuration#TimeFormat, +// GetResetOnFireErrorCode returns the Configuration.ResetOnFireErrorCode. +// Returns true when the router should not respect the handler's error response and +// fire the registered error handler instead. +// +// See https://github.com/kataras/iris/issues/1531 +func (c Configuration) GetResetOnFireErrorCode() bool { + return c.ResetOnFireErrorCode +} + +// GetTimeFormat returns the Configuration.TimeFormat, // format for any kind of datetime parsing. func (c Configuration) GetTimeFormat() string { return c.TimeFormat } -// GetCharset returns the Configuration#Charset, +// GetCharset returns the Configuration.Charset, // the character encoding for various rendering // used for templates and the rest of the responses. func (c Configuration) GetCharset() string { @@ -1203,6 +1220,14 @@ func WithConfiguration(c Configuration) Configurator { main.FireMethodNotAllowed = v } + if v := c.DisableAutoFireStatusCode; v { + main.DisableAutoFireStatusCode = v + } + + if v := c.ResetOnFireErrorCode; v { + main.ResetOnFireErrorCode = v + } + if v := c.DisableBodyConsumptionOnUnmarshal; v { main.DisableBodyConsumptionOnUnmarshal = v } @@ -1211,10 +1236,6 @@ func WithConfiguration(c Configuration) Configurator { main.FireEmptyFormError = v } - if v := c.DisableAutoFireStatusCode; v { - main.DisableAutoFireStatusCode = v - } - if v := c.TimeFormat; v != "" { main.TimeFormat = v } diff --git a/context/application.go b/context/application.go index 8ba980bbb4..6e3f848279 100644 --- a/context/application.go +++ b/context/application.go @@ -59,8 +59,12 @@ type Application interface { // Look core/router/APIBuilder#GetRoutes for more. GetRoutesReadOnly() []RouteReadOnly - // FireErrorCode executes an error http status code handler - // based on the context's status code. + // FireErrorCode handles the response's error response. + // If `Configuration.ResetOnFireErrorCode()` is true + // and the response writer was a recorder or a gzip writer one + // then it will try to reset the headers and the body before calling the + // registered (or default) error handler for that error code set by + // `ctx.StatusCode` method. FireErrorCode(ctx Context) // RouteExists reports whether a particular route exists diff --git a/context/configuration.go b/context/configuration.go index fa0cff0b28..788972ae7a 100644 --- a/context/configuration.go +++ b/context/configuration.go @@ -39,6 +39,18 @@ type ConfigurationReadOnly interface { GetForceLowercaseRouting() bool // GetFireMethodNotAllowed returns the configuration.FireMethodNotAllowed. GetFireMethodNotAllowed() bool + // GetDisableAutoFireStatusCode returns the configuration.DisableAutoFireStatusCode. + // Returns true when the http error status code handler automatic execution turned off. + GetDisableAutoFireStatusCode() bool + // ResetOnFireErrorCode if true then any previously response body or headers through + // response recorder or gzip writer will be ignored and the router + // will fire the registered (or default) HTTP error handler instead. + // See `core/router/handler#FireErrorCode` and `Context.EndRequest` for more details. + // + // Read more at: https://github.com/kataras/iris/issues/1531 + // + // Defaults to false. + GetResetOnFireErrorCode() bool // GetEnableOptimizations returns whether // the application has performance optimizations enabled. @@ -57,9 +69,6 @@ type ConfigurationReadOnly interface { // If true then the `context.ReadBody/ReadForm` will return an `iris.ErrEmptyForm` // on empty request form data. GetFireEmptyFormError() bool - // GetDisableAutoFireStatusCode returns the configuration.DisableAutoFireStatusCode. - // Returns true when the http error status code handler automatic execution turned off. - GetDisableAutoFireStatusCode() bool // GetTimeFormat returns the configuration.TimeFormat, // format for any kind of datetime parsing. diff --git a/context/context.go b/context/context.go index fe8b6f237a..964ddf19be 100644 --- a/context/context.go +++ b/context/context.go @@ -1287,21 +1287,6 @@ func (ctx *context) BeginRequest(w http.ResponseWriter, r *http.Request) { 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. // Do NOT call it manually. Framework calls it automatically. // @@ -1313,29 +1298,9 @@ func (ctx *context) EndRequest() { ctx.deferFunc(ctx) } - if !ctx.Application().ConfigurationReadOnly().GetDisableAutoFireStatusCode() && + if !ctx.app.ConfigurationReadOnly().GetDisableAutoFireStatusCode() && StatusCodeNotSuccessful(ctx.GetStatusCode()) { - // 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.app.FireErrorCode(ctx) } ctx.writer.FlushResponse() @@ -1779,7 +1744,7 @@ func (ctx *context) Method() string { // 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()) + return ctx.RequestPath(ctx.app.ConfigurationReadOnly().GetEnablePathEscape()) } // DecodeQuery returns the uri parameter as url (string) @@ -1861,7 +1826,7 @@ func (ctx *context) Subdomain() (subdomain string) { // listening on mydomain.com:80 // subdomain = mydomain, but it's wrong, it should return "" - vhost := ctx.Application().ConfigurationReadOnly().GetVHost() + vhost := ctx.app.ConfigurationReadOnly().GetVHost() if strings.Contains(vhost, subdomain) { // then it's not subdomain return "" } @@ -1875,7 +1840,7 @@ func (ctx *context) Subdomain() (subdomain string) { // Order may change. // Example: https://github.com/kataras/iris/tree/master/_examples/routing/intelligence/manual func (ctx *context) FindClosest(n int) []string { - return ctx.Application().FindClosestPaths(ctx.Subdomain(), ctx.Path(), n) + return ctx.app.FindClosestPaths(ctx.Subdomain(), ctx.Path(), n) } // IsWWW returns true if the current subdomain (if any) is www. @@ -1883,7 +1848,7 @@ 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) { + if subdomain := host[0:index]; !strings.Contains(ctx.app.ConfigurationReadOnly().GetVHost(), subdomain) { return subdomain == "www" } } @@ -1910,8 +1875,8 @@ const xForwardedForHeaderKey = "X-Forwarded-For" // `Configuration.WithoutRemoteAddrHeader(...)` and // `Configuration.RemoteAddrPrivateSubnets` for more. func (ctx *context) RemoteAddr() string { - remoteHeaders := ctx.Application().ConfigurationReadOnly().GetRemoteAddrHeaders() - privateSubnets := ctx.Application().ConfigurationReadOnly().GetRemoteAddrPrivateSubnets() + remoteHeaders := ctx.app.ConfigurationReadOnly().GetRemoteAddrHeaders() + privateSubnets := ctx.app.ConfigurationReadOnly().GetRemoteAddrPrivateSubnets() for headerName, enabled := range remoteHeaders { if !enabled { @@ -2102,7 +2067,7 @@ func (ctx *context) GetLocale() Locale { } } - if locale := ctx.Application().I18nReadOnly().GetLocale(ctx); locale != nil { + if locale := ctx.app.I18nReadOnly().GetLocale(ctx); locale != nil { ctx.values.Set(contextKey, locale) return locale } @@ -2125,7 +2090,7 @@ func (ctx *context) Tr(format string, args ...interface{}) string { // other nam // SetVersion force-sets the API Version integrated with the "iris/versioning" subpackage. // It can be used inside a middleare. func (ctx *context) SetVersion(constraint string) { - ctx.values.Set(ctx.Application().ConfigurationReadOnly().GetVersionContextKey(), constraint) + ctx.values.Set(ctx.app.ConfigurationReadOnly().GetVersionContextKey(), constraint) } // +------------------------------------------------------------+ @@ -2150,7 +2115,7 @@ func shouldAppendCharset(cType string) bool { func (ctx *context) contentTypeOnce(cType string, charset string) { if charset == "" { - charset = ctx.Application().ConfigurationReadOnly().GetCharset() + charset = ctx.app.ConfigurationReadOnly().GetCharset() } if shouldAppendCharset(cType) { @@ -2180,7 +2145,7 @@ func (ctx *context) ContentType(cType string) { // if doesn't contain a charset already then append it if !strings.Contains(cType, "charset") { if shouldAppendCharset(cType) { - cType += "; charset=" + ctx.Application().ConfigurationReadOnly().GetCharset() + cType += "; charset=" + ctx.app.ConfigurationReadOnly().GetCharset() } } @@ -2457,7 +2422,7 @@ func (ctx *context) FormValues() map[string][]string { // 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) { - return GetForm(ctx.request, ctx.Application().ConfigurationReadOnly().GetPostMaxMemory(), ctx.Application().ConfigurationReadOnly().GetDisableBodyConsumptionOnUnmarshal()) + return GetForm(ctx.request, ctx.app.ConfigurationReadOnly().GetPostMaxMemory(), ctx.app.ConfigurationReadOnly().GetDisableBodyConsumptionOnUnmarshal()) } // GetForm returns the request form (url queries, post or multipart) values. @@ -2661,7 +2626,7 @@ func (ctx *context) FormFile(key string) (multipart.File, *multipart.FileHeader, // 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 { + if err := ctx.request.ParseMultipartForm(ctx.app.ConfigurationReadOnly().GetPostMaxMemory()); err != nil { return nil, nil, err } @@ -2694,7 +2659,7 @@ func (ctx *context) FormFile(key string) (multipart.File, *multipart.FileHeader, // // Example: https://github.com/kataras/iris/tree/master/_examples/file-server/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()) + err = ctx.request.ParseMultipartForm(ctx.app.ConfigurationReadOnly().GetPostMaxMemory()) if err != nil { return 0, err } @@ -2840,7 +2805,7 @@ func (ctx *context) SetMaxRequestBodySize(limitOverBytes int64) { // // However, whenever you can use the `ctx.Request().Body` instead. func (ctx *context) GetBody() ([]byte, error) { - return GetBody(ctx.request, ctx.Application().ConfigurationReadOnly().GetDisableBodyConsumptionOnUnmarshal()) + return GetBody(ctx.request, ctx.app.ConfigurationReadOnly().GetDisableBodyConsumptionOnUnmarshal()) } // GetBody reads and returns the request body. @@ -2907,11 +2872,11 @@ func (ctx *context) UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) e return err } - return ctx.Application().Validate(outPtr) + return ctx.app.Validate(outPtr) } func (ctx *context) shouldOptimize() bool { - return ctx.Application().ConfigurationReadOnly().GetEnableOptimizations() + return ctx.app.ConfigurationReadOnly().GetEnableOptimizations() } // ReadJSON reads JSON from request's body and binds it to a value of any json-valid type. @@ -2962,7 +2927,7 @@ var ErrEmptyForm = errors.New("empty form") func (ctx *context) ReadForm(formObject interface{}) error { values := ctx.FormValues() if len(values) == 0 { - if ctx.Application().ConfigurationReadOnly().GetFireEmptyFormError() { + if ctx.app.ConfigurationReadOnly().GetFireEmptyFormError() { return ErrEmptyForm } return nil @@ -2973,7 +2938,7 @@ func (ctx *context) ReadForm(formObject interface{}) error { return err } - return ctx.Application().Validate(formObject) + return ctx.app.Validate(formObject) } // ReadQuery binds url query to "ptr". The struct field tag is "url". @@ -2990,7 +2955,7 @@ func (ctx *context) ReadQuery(ptr interface{}) error { return err } - return ctx.Application().Validate(ptr) + return ctx.app.Validate(ptr) } // ReadProtobuf binds the body to the "ptr" of a proto Message and returns any error. @@ -3015,7 +2980,7 @@ func (ctx *context) ReadMsgPack(ptr interface{}) error { return err } - return ctx.Application().Validate(ptr) + return ctx.app.Validate(ptr) } // ReadBody binds the request body to the "ptr" depending on the HTTP Method and the Request's Content-Type. @@ -3434,7 +3399,7 @@ const ( // // 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) + ctx.values.Set(ctx.app.ConfigurationReadOnly().GetViewLayoutContextKey(), layoutTmplFile) } // ViewData saves one or more key-value pair in order to be passed if and when .View @@ -3456,7 +3421,7 @@ func (ctx *context) ViewLayout(layoutTmplFile string) { // // 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() + viewDataContextKey := ctx.app.ConfigurationReadOnly().GetViewDataContextKey() if key == "" { ctx.values.Set(viewDataContextKey, value) return @@ -3485,7 +3450,7 @@ func (ctx *context) ViewData(key string, value interface{}) { // 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() + viewDataContextKey := ctx.app.ConfigurationReadOnly().GetViewDataContextKey() v := ctx.values.Get(viewDataContextKey) // if no values found, then return nil @@ -3527,7 +3492,7 @@ func (ctx *context) GetViewData() map[string]interface{} { // 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() + cfg := ctx.app.ConfigurationReadOnly() layout := ctx.values.GetString(cfg.GetViewLayoutContextKey()) @@ -3539,7 +3504,7 @@ func (ctx *context) View(filename string, optionalViewModel ...interface{}) erro bindingData = ctx.values.Get(cfg.GetViewDataContextKey()) } - err := ctx.Application().View(ctx, filename, layout, bindingData) + err := ctx.app.View(ctx, filename, layout, bindingData) if err != nil { ctx.StatusCode(http.StatusInternalServerError) ctx.StopExecution() @@ -3763,7 +3728,7 @@ func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) { } if err != nil { - ctx.Application().Logger().Debugf("JSON: %v", err) + ctx.app.Logger().Debugf("JSON: %v", err) ctx.StatusCode(http.StatusInternalServerError) // it handles the fallback to normal mode here which also removes the gzip headers. return 0, err } @@ -3772,7 +3737,7 @@ func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) { n, err = WriteJSON(ctx.writer, v, options, ctx.shouldOptimize()) if err != nil { - ctx.Application().Logger().Debugf("JSON: %v", err) + ctx.app.Logger().Debugf("JSON: %v", err) ctx.StatusCode(http.StatusInternalServerError) return 0, err } @@ -3838,7 +3803,7 @@ func (ctx *context) JSONP(v interface{}, opts ...JSONP) (int, error) { n, err := WriteJSONP(ctx.writer, v, options, ctx.shouldOptimize()) if err != nil { - ctx.Application().Logger().Debugf("JSONP: %v", err) + ctx.app.Logger().Debugf("JSONP: %v", err) ctx.StatusCode(http.StatusInternalServerError) return 0, err } @@ -3935,7 +3900,7 @@ func (ctx *context) XML(v interface{}, opts ...XML) (int, error) { n, err := WriteXML(ctx.writer, v, options, ctx.shouldOptimize()) if err != nil { - ctx.Application().Logger().Debugf("XML: %v", err) + ctx.app.Logger().Debugf("XML: %v", err) ctx.StatusCode(http.StatusInternalServerError) return 0, err } @@ -4015,7 +3980,7 @@ func (ctx *context) Markdown(markdownB []byte, opts ...Markdown) (int, error) { n, err := WriteMarkdown(ctx.writer, markdownB, options) if err != nil { - ctx.Application().Logger().Debugf("Markdown: %v", err) + ctx.app.Logger().Debugf("Markdown: %v", err) ctx.StatusCode(http.StatusInternalServerError) return 0, err } @@ -4027,7 +3992,7 @@ func (ctx *context) Markdown(markdownB []byte, opts ...Markdown) (int, error) { func (ctx *context) YAML(v interface{}) (int, error) { out, err := yaml.Marshal(v) if err != nil { - ctx.Application().Logger().Debugf("YAML: %v", err) + ctx.app.Logger().Debugf("YAML: %v", err) ctx.StatusCode(http.StatusInternalServerError) return 0, err } @@ -4225,7 +4190,7 @@ func (ctx *context) Negotiate(v interface{}) (int, error) { } if charset == "" { - charset = ctx.Application().ConfigurationReadOnly().GetCharset() + charset = ctx.app.ConfigurationReadOnly().GetCharset() } if encoding == "gzip" { @@ -5410,7 +5375,7 @@ func (ctx *context) BeginTransaction(pipe func(t *Transaction)) { 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(fmt.Errorf("recovery from panic: %w", ErrTransactionInterrupt)) + ctx.app.Logger().Warn(fmt.Errorf("recovery from panic: %w", ErrTransactionInterrupt)) // complete (again or not , doesn't matters) the scope without loud t.Complete(nil) // we continue as normal, no need to return here* @@ -5498,7 +5463,7 @@ func (ctx *context) Exec(method string, path string) { // execute the route from the (internal) context router // this way we keep the sessions and the values - ctx.Application().ServeHTTPC(ctx) + ctx.app.ServeHTTPC(ctx) // set the request back to its previous state req.RequestURI = backupPath @@ -5513,7 +5478,7 @@ func (ctx *context) Exec(method string, 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. func (ctx *context) RouteExists(method, path string) bool { - return ctx.Application().RouteExists(ctx, method, path) + return ctx.app.RouteExists(ctx, method, path) } const ( diff --git a/context/gzip_response_writer.go b/context/gzip_response_writer.go index b825e5d718..fe2aa8a051 100644 --- a/context/gzip_response_writer.go +++ b/context/gzip_response_writer.go @@ -117,8 +117,9 @@ func (w *GzipResponseWriter) Write(contents []byte) (int, error) { func (w *GzipResponseWriter) Writef(format string, a ...interface{}) (n int, err error) { n, err = fmt.Fprintf(w, format, a...) if err == nil { - if w.ResponseWriter.Header()[ContentTypeHeaderKey] == nil { - w.ResponseWriter.Header().Set(ContentTypeHeaderKey, ContentTextHeaderValue) + h := w.ResponseWriter.Header() + if h[ContentTypeHeaderKey] == nil { + h.Set(ContentTypeHeaderKey, ContentTextHeaderValue) } } @@ -130,8 +131,9 @@ func (w *GzipResponseWriter) Writef(format string, a ...interface{}) (n int, err func (w *GzipResponseWriter) WriteString(s string) (n int, err error) { n, err = w.Write([]byte(s)) if err == nil { - if w.ResponseWriter.Header()[ContentTypeHeaderKey] == nil { - w.ResponseWriter.Header().Set(ContentTypeHeaderKey, ContentTextHeaderValue) + h := w.ResponseWriter.Header() + if h[ContentTypeHeaderKey] == nil { + h.Set(ContentTypeHeaderKey, ContentTextHeaderValue) } } return @@ -183,11 +185,10 @@ func AddGzipHeaders(w ResponseWriter) { w.Header().Add(ContentEncodingHeaderKey, GzipHeaderValue) } -// FlushResponse validates the response headers in order to be compatible with the gzip written data -// and writes the data to the underline ResponseWriter. -func (w *GzipResponseWriter) FlushResponse() { - _, _ = w.WriteNow(w.chunks) - w.ResponseWriter.FlushResponse() +// Body returns the body tracked from the writer so far, +// do not use this for edit. +func (w *GzipResponseWriter) Body() []byte { + return w.chunks } // ResetBody resets the response body. @@ -200,3 +201,10 @@ func (w *GzipResponseWriter) ResetBody() { func (w *GzipResponseWriter) Disable() { w.disabled = true } + +// FlushResponse validates the response headers in order to be compatible with the gzip written data +// and writes the data to the underline ResponseWriter. +func (w *GzipResponseWriter) FlushResponse() { + _, _ = w.WriteNow(w.chunks) + w.ResponseWriter.FlushResponse() +} diff --git a/context/response_recorder.go b/context/response_recorder.go index 5a0840b97e..a71a56e980 100644 --- a/context/response_recorder.go +++ b/context/response_recorder.go @@ -114,7 +114,7 @@ func (w *ResponseRecorder) SetBodyString(s string) { w.SetBody([]byte(s)) } -// Body returns the body tracked from the writer so far +// Body returns the body tracked from the writer so far, // do not use this for edit. func (w *ResponseRecorder) Body() []byte { return w.chunks @@ -190,7 +190,7 @@ func (w *ResponseRecorder) Clone() ResponseWriter { func (w *ResponseRecorder) WriteTo(res ResponseWriter) { if to, ok := res.(*ResponseRecorder); ok { - // set the status code, to is first ( probably an error? (context.StatusCodeNotSuccessful, defaults to < 200 || >= 400). + // set the status code, to is first ( probably an error? (context.StatusCodeNotSuccessful, defaults to >=400). if statusCode := w.ResponseWriter.StatusCode(); statusCode == defaultStatusCode { to.WriteHeader(statusCode) } diff --git a/context/status.go b/context/status.go new file mode 100644 index 0000000000..59bd8ebea6 --- /dev/null +++ b/context/status.go @@ -0,0 +1,117 @@ +package context + +import "net/http" + +// ClientErrorCodes holds the 4xx Client errors. +var ( + ClientErrorCodes = []int{ + http.StatusBadRequest, + http.StatusUnauthorized, + http.StatusPaymentRequired, + http.StatusForbidden, + http.StatusNotFound, + http.StatusMethodNotAllowed, + http.StatusNotAcceptable, + http.StatusProxyAuthRequired, + http.StatusRequestTimeout, + http.StatusConflict, + http.StatusGone, + http.StatusLengthRequired, + http.StatusPreconditionFailed, + http.StatusRequestEntityTooLarge, + http.StatusRequestURITooLong, + http.StatusUnsupportedMediaType, + http.StatusRequestedRangeNotSatisfiable, + http.StatusExpectationFailed, + http.StatusTeapot, + http.StatusMisdirectedRequest, + http.StatusUnprocessableEntity, + http.StatusLocked, + http.StatusFailedDependency, + http.StatusTooEarly, + http.StatusUpgradeRequired, + http.StatusPreconditionRequired, + http.StatusTooManyRequests, + http.StatusRequestHeaderFieldsTooLarge, + http.StatusUnavailableForLegalReasons, + // Unofficial. + StatusPageExpired, + StatusBlockedByWindowsParentalControls, + StatusInvalidToken, + StatusTokenRequired, + } + // ServerErrorCodes holds the 5xx Server errors. + ServerErrorCodes = []int{ + http.StatusInternalServerError, + http.StatusNotImplemented, + http.StatusBadGateway, + http.StatusServiceUnavailable, + http.StatusGatewayTimeout, + http.StatusHTTPVersionNotSupported, + http.StatusVariantAlsoNegotiates, + http.StatusInsufficientStorage, + http.StatusLoopDetected, + http.StatusNotExtended, + http.StatusNetworkAuthenticationRequired, + // Unofficial. + StatusBandwidthLimitExceeded, + StatusInvalidSSLCertificate, + StatusSiteOverloaded, + StatusSiteFrozen, + StatusNetworkReadTimeout, + } + + // ClientAndServerErrorCodes is the static list of all client and server error codes. + ClientAndServerErrorCodes = append(ClientErrorCodes, ServerErrorCodes...) +) + +// Unofficial status error codes. +const ( + // 4xx + StatusPageExpired = 419 + StatusBlockedByWindowsParentalControls = 450 + StatusInvalidToken = 498 + StatusTokenRequired = 499 + // 5xx + StatusBandwidthLimitExceeded = 509 + StatusInvalidSSLCertificate = 526 + StatusSiteOverloaded = 529 + StatusSiteFrozen = 530 + StatusNetworkReadTimeout = 598 +) + +var unofficialStatusText = map[int]string{ + StatusPageExpired: "Page Expired", + StatusBlockedByWindowsParentalControls: "Blocked by Windows Parental Controls", + StatusInvalidToken: "Invalid Token", + StatusTokenRequired: "Token Required", + StatusBandwidthLimitExceeded: "Bandwidth Limit Exceeded", + StatusInvalidSSLCertificate: "Invalid SSL Certificate", + StatusSiteOverloaded: "Site is overloaded", + StatusSiteFrozen: "Site is frozen", + StatusNetworkReadTimeout: "Network read timeout error", +} + +// StatusText returns a text for the HTTP status code. It returns the empty +// string if the code is unknown. +func StatusText(code int) string { + text := http.StatusText(code) + if text == "" { + text = unofficialStatusText[code] + } + + return text +} + +// StatusCodeNotSuccessful defines if a specific "statusCode" is not +// a valid status code for a successful response. +// By default if the status code is lower than 400 then it is not a failure one, +// otherwise it is considered as an error code. +// +// Read more at `iris/Configuration#DisableAutoFireStatusCode` and +// `iris/core/router/Party#OnAnyErrorCode` for relative information. +// +// +// Modify this variable when your Iris server or/and client +// not follows the RFC: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html +var StatusCodeNotSuccessful = func(statusCode int) bool { return statusCode >= 400 } diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 7453a03155..ce54681065 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -984,109 +984,11 @@ func (api *APIBuilder) OnErrorCode(statusCode int, handlers ...context.Handler) return } -// ClientErrorCodes holds the 4xx Client errors. -var ( - ClientErrorCodes = []int{ - http.StatusBadRequest, - http.StatusUnauthorized, - http.StatusPaymentRequired, - http.StatusForbidden, - http.StatusNotFound, - http.StatusMethodNotAllowed, - http.StatusNotAcceptable, - http.StatusProxyAuthRequired, - http.StatusRequestTimeout, - http.StatusConflict, - http.StatusGone, - http.StatusLengthRequired, - http.StatusPreconditionFailed, - http.StatusRequestEntityTooLarge, - http.StatusRequestURITooLong, - http.StatusUnsupportedMediaType, - http.StatusRequestedRangeNotSatisfiable, - http.StatusExpectationFailed, - http.StatusTeapot, - http.StatusMisdirectedRequest, - http.StatusUnprocessableEntity, - http.StatusLocked, - http.StatusFailedDependency, - http.StatusTooEarly, - http.StatusUpgradeRequired, - http.StatusPreconditionRequired, - http.StatusTooManyRequests, - http.StatusRequestHeaderFieldsTooLarge, - http.StatusUnavailableForLegalReasons, - // Unofficial. - StatusPageExpired, - StatusBlockedByWindowsParentalControls, - StatusInvalidToken, - StatusTokenRequired, - } - // ServerErrorCodes holds the 5xx Server errors. - ServerErrorCodes = []int{ - http.StatusInternalServerError, - http.StatusNotImplemented, - http.StatusBadGateway, - http.StatusServiceUnavailable, - http.StatusGatewayTimeout, - http.StatusHTTPVersionNotSupported, - http.StatusVariantAlsoNegotiates, - http.StatusInsufficientStorage, - http.StatusLoopDetected, - http.StatusNotExtended, - http.StatusNetworkAuthenticationRequired, - // Unofficial. - StatusBandwidthLimitExceeded, - StatusInvalidSSLCertificate, - StatusSiteOverloaded, - StatusSiteFrozen, - StatusNetworkReadTimeout, - } -) - -// Unofficial status error codes. -const ( - // 4xx - StatusPageExpired = 419 - StatusBlockedByWindowsParentalControls = 450 - StatusInvalidToken = 498 - StatusTokenRequired = 499 - // 5xx - StatusBandwidthLimitExceeded = 509 - StatusInvalidSSLCertificate = 526 - StatusSiteOverloaded = 529 - StatusSiteFrozen = 530 - StatusNetworkReadTimeout = 598 -) - -var unofficialStatusText = map[int]string{ - StatusPageExpired: "Page Expired", - StatusBlockedByWindowsParentalControls: "Blocked by Windows Parental Controls", - StatusInvalidToken: "Invalid Token", - StatusTokenRequired: "Token Required", - StatusBandwidthLimitExceeded: "Bandwidth Limit Exceeded", - StatusInvalidSSLCertificate: "Invalid SSL Certificate", - StatusSiteOverloaded: "Site is overloaded", - StatusSiteFrozen: "Site is frozen", - StatusNetworkReadTimeout: "Network read timeout error", -} - -// StatusText returns a text for the HTTP status code. It returns the empty -// string if the code is unknown. -func StatusText(code int) string { - text := http.StatusText(code) - if text == "" { - text = unofficialStatusText[code] - } - - return text -} - // OnAnyErrorCode registers a handlers chain for all error codes -// (4xxx and 5xxx, change the `ClientErrorCodes` and `ServerErrorCodes` variables to modify those) +// (4xxx and 5xxx, change the `context.ClientErrorCodes` and `context.ServerErrorCodes` variables to modify those) // Look `OnErrorCode` too. func (api *APIBuilder) OnAnyErrorCode(handlers ...context.Handler) (routes []*Route) { - for _, statusCode := range append(ClientErrorCodes, ServerErrorCodes...) { + for _, statusCode := range context.ClientAndServerErrorCodes { routes = append(routes, api.OnErrorCode(statusCode, handlers...)...) } diff --git a/core/router/handler.go b/core/router/handler.go index c59bb70779..580f921082 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -35,6 +35,8 @@ type ( // HTTPErrorHandler should contain a method `FireErrorCode` which // handles http unsuccessful status codes. HTTPErrorHandler interface { + // FireErrorCode should send an error response to the client based + // on the given context's response status code. FireErrorCode(ctx context.Context) } ) @@ -437,27 +439,55 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { ctx.StatusCode(http.StatusNotFound) } +func statusCodeSuccessful(statusCode int) bool { + return !context.StatusCodeNotSuccessful(statusCode) +} + +// FireErrorCode handles the response's error response. +// If `Configuration.ResetOnFireErrorCode()` is true +// and the response writer was a recorder or a gzip writer one +// then it will try to reset the headers and the body before calling the +// registered (or default) error handler for that error code set by +// `ctx.StatusCode` method. func (h *routerHandler) FireErrorCode(ctx context.Context) { + // On common response writer, always check + // if we can't reset the body and the body has been filled + // which means that the status code already sent, + // then do not fire this custom error code, + // rel: context/context.go#EndRequest. + // + // Note that, this is set to 0 on recorder and gzip writer because they cache the response, + // so we check their len(Body()) instead, look below. + if ctx.ResponseWriter().Written() > 0 { + return + } + statusCode := ctx.GetStatusCode() // the response's cached one. - // if we can reset the body - if w, ok := ctx.IsRecording(); ok { - if statusCodeSuccessful(w.StatusCode()) { // if not an error status code - w.WriteHeader(statusCode) // then set it manually here, otherwise it should be set via ctx.StatusCode(...) + if ctx.Application().ConfigurationReadOnly().GetResetOnFireErrorCode() /* could be an argument too but we must not break the method */ { + // if we can reset the body, probably manual call of `Application.FireErrorCode`. + if w, ok := ctx.IsRecording(); ok { + if statusCodeSuccessful(w.StatusCode()) { // if not an error status code + w.WriteHeader(statusCode) // then set it manually here, otherwise it should be set via ctx.StatusCode(...) + } + // reset if previous content and it's recorder, keep the status code. + w.ClearHeaders() + w.ResetBody() + } else if w, ok := ctx.ResponseWriter().(*context.GzipResponseWriter); ok { + // reset and disable the gzip in order to be an expected form of http error result + w.ResetBody() + w.Disable() } - // reset if previous content and it's recorder, keep the status code. - w.ClearHeaders() - w.ResetBody() - } else if w, ok := ctx.ResponseWriter().(*context.GzipResponseWriter); ok { - // reset and disable the gzip in order to be an expected form of http error result - w.ResetBody() - w.Disable() } else { - // if we can't reset the body and the body has been filled - // which means that the status code already sent, - // then do not fire this custom error code. - if ctx.ResponseWriter().Written() > 0 { // != -1, rel: context/context.go#EndRequest - return + // check if a body already set (the error response is handled by the handler itself, see `Context.EndRequest`) + if w, ok := ctx.IsRecording(); ok { + if len(w.Body()) > 0 { + return + } + } else if w, ok := ctx.ResponseWriter().(*context.GzipResponseWriter); ok { + if len(w.Body()) > 0 { + return + } } } @@ -523,11 +553,7 @@ func (h *routerHandler) FireErrorCode(ctx context.Context) { } // not error handler found, write a default message. - ctx.WriteString(StatusText(statusCode)) -} - -func statusCodeSuccessful(statusCode int) bool { - return !context.StatusCodeNotSuccessful(statusCode) + ctx.WriteString(context.StatusText(statusCode)) } func (h *routerHandler) subdomainAndPathAndMethodExists(ctx context.Context, t *trie, method, path string) bool { diff --git a/core/router/status_test.go b/core/router/status_test.go index 11e27cd4ba..1b25bf36bc 100644 --- a/core/router/status_test.go +++ b/core/router/status_test.go @@ -35,11 +35,11 @@ func TestOnAnyErrorCode(t *testing.T) { ctx.WriteString(expectedFoundResponse) }) - app.Get("/406", func(ctx context.Context) { + expected407 := "this should be sent, we manage the response response by ourselves" + app.Get("/407", func(ctx context.Context) { ctx.Record() - ctx.WriteString("this should not be sent, only status text will be sent") - ctx.WriteString("the handler can handle 'rollback' of the text when error code fired because of the recorder") - ctx.StatusCode(iris.StatusNotAcceptable) + ctx.WriteString(expected407) + ctx.StatusCode(iris.StatusProxyAuthRequired) }) e := httptest.New(t, app) @@ -57,7 +57,26 @@ func TestOnAnyErrorCode(t *testing.T) { checkAndClearBuf(t, buff, expectedPrintBeforeExecuteErr) - e.GET("/406").Expect().Status(iris.StatusNotAcceptable). + e.GET("/407").Expect().Status(iris.StatusProxyAuthRequired). + Body().Equal(expected407) + + // Test Configuration.ResetOnFireErrorCode. + app2 := iris.New() + app2.Configure(iris.WithResetOnFireErrorCode) + + app2.OnAnyErrorCode(func(ctx context.Context) { + buff.WriteString(expectedPrintBeforeExecuteErr) + ctx.Next() + }, defaultErrHandler) + + app2.Get("/406", func(ctx context.Context) { + ctx.Record() + ctx.WriteString("this should not be sent, only status text will be sent") + ctx.WriteString("the handler can handle 'rollback' of the text when error code fired because of the recorder") + ctx.StatusCode(iris.StatusNotAcceptable) + }) + + httptest.New(t, app2).GET("/406").Expect().Status(iris.StatusNotAcceptable). Body().Equal(http.StatusText(iris.StatusNotAcceptable)) checkAndClearBuf(t, buff, expectedPrintBeforeExecuteErr) diff --git a/iris.go b/iris.go index 44904d0519..0ffd432a26 100644 --- a/iris.go +++ b/iris.go @@ -95,10 +95,10 @@ const ( StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5 StatusUnavailableForLegalReasons = 451 // RFC 7725, 3 // Unofficial Client Errors. - StatusPageExpired = router.StatusPageExpired - StatusBlockedByWindowsParentalControls = router.StatusBlockedByWindowsParentalControls - StatusInvalidToken = router.StatusInvalidToken - StatusTokenRequired = router.StatusTokenRequired + StatusPageExpired = context.StatusPageExpired + StatusBlockedByWindowsParentalControls = context.StatusBlockedByWindowsParentalControls + StatusInvalidToken = context.StatusInvalidToken + StatusTokenRequired = context.StatusTokenRequired // StatusInternalServerError = 500 // RFC 7231, 6.6.1 StatusNotImplemented = 501 // RFC 7231, 6.6.2 @@ -112,18 +112,18 @@ const ( StatusNotExtended = 510 // RFC 2774, 7 StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6 // Unofficial Server Errors. - StatusBandwidthLimitExceeded = router.StatusBandwidthLimitExceeded - StatusInvalidSSLCertificate = router.StatusInvalidSSLCertificate - StatusSiteOverloaded = router.StatusSiteOverloaded - StatusSiteFrozen = router.StatusSiteFrozen - StatusNetworkReadTimeout = router.StatusNetworkReadTimeout + StatusBandwidthLimitExceeded = context.StatusBandwidthLimitExceeded + StatusInvalidSSLCertificate = context.StatusInvalidSSLCertificate + StatusSiteOverloaded = context.StatusSiteOverloaded + StatusSiteFrozen = context.StatusSiteFrozen + StatusNetworkReadTimeout = context.StatusNetworkReadTimeout ) // StatusText returns a text for the HTTP status code. It returns the empty // string if the code is unknown. // // Shortcut for core/router#StatusText. -var StatusText = router.StatusText +var StatusText = context.StatusText // HTTP Methods copied from `net/http`. const (