new Application.SetContextErrorHandler method

This commit is contained in:
Gerasimos (Makis) Maropoulos 2022-04-13 01:00:53 +03:00
parent 73dfabf412
commit ae828d8db9
No known key found for this signature in database
GPG Key ID: 66FCC29BD385FCA6
7 changed files with 314 additions and 243 deletions

View File

@ -1,8 +1,9 @@
version: 2
version: 3
cli:
server: https://app.fossa.com
fetcher: custom
project: https://github.com/kataras/iris.git
fetcher: git
package: github.com/kataras/iris
project: github.com/kataras/iris
analyze:
modules:
- name: iris

View File

@ -28,11 +28,12 @@ The codebase for Dependency Injection, Internationalization and localization and
## Fixes and Improvements
- Add new `Application.SetContextErrorHandler` to globally customize the default behavior (status code 500 without body) on `JSON`, `JSONP`, `Protobuf`, `MsgPack`, `XML`, `YAML` and `Markdown` method call write errors instead of catching the error on each handler.
- Add new [x/pagination](x/pagination/pagination.go) sub-package which supports generics code (go 1.18+).
- Add new [middleware/modrevision](middleware/modrevision) middleware (example at [_examples/project/api/router.go]_examples/project/api/router.go).
- Add `iris.BuildRevision` and `iris.BuildTime` to embrace the new go's 1.18 debug build information.
- Add `Context.SetJSONOptions` to customize on a higher level the JSON options on `Context.JSON` calls.
- ~Add `Context.SetJSONOptions` to customize on a higher level the JSON options on `Context.JSON` calls.~ update: remains as it's, per JSON call.
- Add new [auth](auth) sub-package which helps on any user type auth using JWT (access & refresh tokens) and a cookie (optional).
- Add `Party.EnsureStaticBindings` which, if called, the MVC binder panics if a struct's input binding depends on the HTTP request data instead of a static dependency. This is useful to make sure your API crafted through `Party.PartyConfigure` depends only on struct values you already defined at `Party.RegisterDependency` == will never use reflection at serve-time (maximum performance).
@ -71,7 +72,7 @@ The codebase for Dependency Injection, Internationalization and localization and
- New `apps.OnApplicationRegistered` method which listens on new Iris applications hosted under the same binary. Use it on your `init` functions to configure Iris applications by any spot in your project's files.
- `Context.JSON` respects any object implements the `easyjson.Marshaler` interface and renders the result using the [easyjon](https://github.com/mailru/easyjson)'s writer.
- `Context.JSON` respects any object implements the `easyjson.Marshaler` interface and renders the result using the [easyjon](https://github.com/mailru/easyjson)'s writer. **Set** the `Configuration.EnableProtoJSON` and `Configuration.EnableEasyJSON` to true in order to enable this feature.
- minor: `Context` structure implements the standard go Context interface now (includes: Deadline, Done, Err and Value methods). Handlers can now just pass the `ctx iris.Context` as a shortcut of `ctx.Request().Context()` when needed.

View File

@ -10,11 +10,11 @@ import (
"strings"
"time"
"github.com/kataras/golog"
"github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/core/netutil"
"github.com/BurntSushi/toml"
"github.com/kataras/golog"
"github.com/kataras/sitemap"
"github.com/kataras/tunnel"
"gopkg.in/yaml.v3"
@ -317,6 +317,20 @@ var WithOptimizations = func(app *Application) {
app.config.EnableOptimizations = true
}
// WithProtoJSON enables the proto marshaler on Context.JSON method.
//
// See `Configuration` for more.
var WithProtoJSON = func(app *Application) {
app.config.EnableProtoJSON = true
}
// WithEasyJSON enables the fast easy json marshaler on Context.JSON method.
//
// See `Configuration` for more.
var WithEasyJSON = func(app *Application) {
app.config.EnableEasyJSON = true
}
// WithFireMethodNotAllowed enables the FireMethodNotAllowed setting.
//
// See `Configuration`.
@ -740,6 +754,17 @@ type Configuration struct {
//
// Defaults to false.
EnableOptimizations bool `ini:"enable_optimizations" json:"enableOptimizations,omitempty" yaml:"EnableOptimizations" toml:"EnableOptimizations"`
// EnableProtoJSON when this field is true
// enables the proto marshaler on given proto messages when calling the Context.JSON method.
//
// Defaults to false.
EnableProtoJSON bool `ini:"enable_proto_json" json:"enableProtoJSON,omitempty" yaml:"EnableProtoJSON" toml:"EnableProtoJSON"`
// EnableEasyJSON when this field is true
// enables the fast easy json marshaler on compatible struct values when calling the Context.JSON method.
//
// Defaults to false.
EnableEasyJSON bool `ini:"enable_easy_json" json:"enableEasyJSON,omitempty" yaml:"EnableEasyJSON" toml:"EnableEasyJSON"`
// DisableBodyConsumptionOnUnmarshal manages the reading behavior of the context's body readers/binders.
// If set to true then it
// disables the body consumption by the `context.UnmarshalBody/ReadJSON/ReadXML`.
@ -915,177 +940,187 @@ type Configuration struct {
var _ context.ConfigurationReadOnly = (*Configuration)(nil)
// GetVHost returns the non-exported vhost config field.
func (c Configuration) GetVHost() string {
func (c *Configuration) GetVHost() string {
return c.vhost
}
// GetLogLevel returns the LogLevel field.
func (c Configuration) GetLogLevel() string {
func (c *Configuration) GetLogLevel() string {
return c.vhost
}
// GetSocketSharding returns the SocketSharding field.
func (c Configuration) GetSocketSharding() bool {
func (c *Configuration) GetSocketSharding() bool {
return c.SocketSharding
}
// GetKeepAlive returns the KeepAlive field.
func (c Configuration) GetKeepAlive() time.Duration {
func (c *Configuration) GetKeepAlive() time.Duration {
return c.KeepAlive
}
// GetKeepAlive returns the Timeout field.
func (c Configuration) GetTimeout() time.Duration {
func (c *Configuration) GetTimeout() time.Duration {
return c.Timeout
}
// GetKeepAlive returns the TimeoutMessage field.
func (c Configuration) GetTimeoutMessage() string {
func (c *Configuration) GetTimeoutMessage() string {
return c.TimeoutMessage
}
// GetDisablePathCorrection returns the DisablePathCorrection field.
func (c Configuration) GetDisablePathCorrection() bool {
func (c *Configuration) GetDisablePathCorrection() bool {
return c.DisablePathCorrection
}
// GetDisablePathCorrectionRedirection returns the DisablePathCorrectionRedirection field.
func (c Configuration) GetDisablePathCorrectionRedirection() bool {
func (c *Configuration) GetDisablePathCorrectionRedirection() bool {
return c.DisablePathCorrectionRedirection
}
// GetEnablePathIntelligence returns the EnablePathIntelligence field.
func (c Configuration) GetEnablePathIntelligence() bool {
func (c *Configuration) GetEnablePathIntelligence() bool {
return c.EnablePathIntelligence
}
// GetEnablePathEscape returns the EnablePathEscape field.
func (c Configuration) GetEnablePathEscape() bool {
func (c *Configuration) GetEnablePathEscape() bool {
return c.EnablePathEscape
}
// GetForceLowercaseRouting returns the ForceLowercaseRouting field.
func (c Configuration) GetForceLowercaseRouting() bool {
func (c *Configuration) GetForceLowercaseRouting() bool {
return c.ForceLowercaseRouting
}
// GetFireMethodNotAllowed returns the FireMethodNotAllowed field.
func (c Configuration) GetFireMethodNotAllowed() bool {
func (c *Configuration) GetFireMethodNotAllowed() bool {
return c.FireMethodNotAllowed
}
// GetEnableOptimizations returns the EnableOptimizations.
func (c Configuration) GetEnableOptimizations() bool {
func (c *Configuration) GetEnableOptimizations() bool {
return c.EnableOptimizations
}
// GetEnableProtoJSON returns the EnableProtoJSON field.
func (c *Configuration) GetEnableProtoJSON() bool {
return c.EnableProtoJSON
}
// GetEnableEasyJSON returns the EnableEasyJSON field.
func (c *Configuration) GetEnableEasyJSON() bool {
return c.EnableEasyJSON
}
// GetDisableBodyConsumptionOnUnmarshal returns the DisableBodyConsumptionOnUnmarshal field.
func (c Configuration) GetDisableBodyConsumptionOnUnmarshal() bool {
func (c *Configuration) GetDisableBodyConsumptionOnUnmarshal() bool {
return c.DisableBodyConsumptionOnUnmarshal
}
// GetFireEmptyFormError returns the DisableBodyConsumptionOnUnmarshal field.
func (c Configuration) GetFireEmptyFormError() bool {
func (c *Configuration) GetFireEmptyFormError() bool {
return c.FireEmptyFormError
}
// GetDisableAutoFireStatusCode returns the DisableAutoFireStatusCode field.
func (c Configuration) GetDisableAutoFireStatusCode() bool {
func (c *Configuration) GetDisableAutoFireStatusCode() bool {
return c.DisableAutoFireStatusCode
}
// GetResetOnFireErrorCode returns ResetOnFireErrorCode field.
func (c Configuration) GetResetOnFireErrorCode() bool {
func (c *Configuration) GetResetOnFireErrorCode() bool {
return c.ResetOnFireErrorCode
}
// GetTimeFormat returns the TimeFormat field.
func (c Configuration) GetTimeFormat() string {
func (c *Configuration) GetTimeFormat() string {
return c.TimeFormat
}
// GetCharset returns the Charset field.
func (c Configuration) GetCharset() string {
func (c *Configuration) GetCharset() string {
return c.Charset
}
// GetPostMaxMemory returns the PostMaxMemory field.
func (c Configuration) GetPostMaxMemory() int64 {
func (c *Configuration) GetPostMaxMemory() int64 {
return c.PostMaxMemory
}
// GetLocaleContextKey returns the LocaleContextKey field.
func (c Configuration) GetLocaleContextKey() string {
func (c *Configuration) GetLocaleContextKey() string {
return c.LocaleContextKey
}
// GetLanguageContextKey returns the LanguageContextKey field.
func (c Configuration) GetLanguageContextKey() string {
func (c *Configuration) GetLanguageContextKey() string {
return c.LanguageContextKey
}
// GetLanguageInputContextKey returns the LanguageInputContextKey field.
func (c Configuration) GetLanguageInputContextKey() string {
func (c *Configuration) GetLanguageInputContextKey() string {
return c.LanguageInputContextKey
}
// GetVersionContextKey returns the VersionContextKey field.
func (c Configuration) GetVersionContextKey() string {
func (c *Configuration) GetVersionContextKey() string {
return c.VersionContextKey
}
// GetVersionAliasesContextKey returns the VersionAliasesContextKey field.
func (c Configuration) GetVersionAliasesContextKey() string {
func (c *Configuration) GetVersionAliasesContextKey() string {
return c.VersionAliasesContextKey
}
// GetViewEngineContextKey returns the ViewEngineContextKey field.
func (c Configuration) GetViewEngineContextKey() string {
func (c *Configuration) GetViewEngineContextKey() string {
return c.ViewEngineContextKey
}
// GetViewLayoutContextKey returns the ViewLayoutContextKey field.
func (c Configuration) GetViewLayoutContextKey() string {
func (c *Configuration) GetViewLayoutContextKey() string {
return c.ViewLayoutContextKey
}
// GetViewDataContextKey returns the ViewDataContextKey field.
func (c Configuration) GetViewDataContextKey() string {
func (c *Configuration) GetViewDataContextKey() string {
return c.ViewDataContextKey
}
// GetFallbackViewContextKey returns the FallbackViewContextKey field.
func (c Configuration) GetFallbackViewContextKey() string {
func (c *Configuration) GetFallbackViewContextKey() string {
return c.FallbackViewContextKey
}
// GetRemoteAddrHeaders returns the RemoteAddrHeaders field.
func (c Configuration) GetRemoteAddrHeaders() []string {
func (c *Configuration) GetRemoteAddrHeaders() []string {
return c.RemoteAddrHeaders
}
// GetRemoteAddrHeadersForce returns RemoteAddrHeadersForce field.
func (c Configuration) GetRemoteAddrHeadersForce() bool {
func (c *Configuration) GetRemoteAddrHeadersForce() bool {
return c.RemoteAddrHeadersForce
}
// GetSSLProxyHeaders returns the SSLProxyHeaders field.
func (c Configuration) GetSSLProxyHeaders() map[string]string {
func (c *Configuration) GetSSLProxyHeaders() map[string]string {
return c.SSLProxyHeaders
}
// GetRemoteAddrPrivateSubnets returns the RemoteAddrPrivateSubnets field.
func (c Configuration) GetRemoteAddrPrivateSubnets() []netutil.IPRange {
func (c *Configuration) GetRemoteAddrPrivateSubnets() []netutil.IPRange {
return c.RemoteAddrPrivateSubnets
}
// GetHostProxyHeaders returns the HostProxyHeaders field.
func (c Configuration) GetHostProxyHeaders() map[string]bool {
func (c *Configuration) GetHostProxyHeaders() map[string]bool {
return c.HostProxyHeaders
}
// GetOther returns the Other field.
func (c Configuration) GetOther() map[string]interface{} {
func (c *Configuration) GetOther() map[string]interface{} {
return c.Other
}
@ -1166,6 +1201,14 @@ func WithConfiguration(c Configuration) Configurator {
main.EnableOptimizations = v
}
if v := c.EnableProtoJSON; v {
main.EnableProtoJSON = v
}
if v := c.EnableEasyJSON; v {
main.EnableEasyJSON = v
}
if v := c.FireMethodNotAllowed; v {
main.FireMethodNotAllowed = v
}
@ -1342,6 +1385,8 @@ func DefaultConfiguration() Configuration {
SSLProxyHeaders: make(map[string]string),
HostProxyHeaders: make(map[string]bool),
EnableOptimizations: false,
EnableProtoJSON: false,
EnableEasyJSON: false,
Other: make(map[string]interface{}),
}
}

View File

@ -52,6 +52,10 @@ type Application interface {
// is hijacked by a third-party middleware and the http handler return too fast.
GetContextPool() *Pool
// GetContextErrorHandler returns the handler which handles errors
// on JSON write failures.
GetContextErrorHandler() ErrorHandler
// ServeHTTPC is the internal router, it's visible because it can be used for advanced use cases,
// i.e: routing within a foreign context.
//

View File

@ -43,6 +43,11 @@ type ConfigurationReadOnly interface {
// GetEnableOptimizations returns the EnableOptimizations field.
GetEnableOptimizations() bool
// GetEnableProtoJSON returns the EnableProtoJSON field.
GetEnableProtoJSON() bool
// GetEnableEasyJSON returns the EnableEasyJSON field.
GetEnableEasyJSON() bool
// GetDisableBodyConsumptionOnUnmarshal returns the DisableBodyConsumptionOnUnmarshal field.
GetDisableBodyConsumptionOnUnmarshal() bool
// GetFireEmptyFormError returns the FireEmptyFormError field.

View File

@ -3046,6 +3046,9 @@ func (ctx *Context) ReadBody(ptr interface{}) error {
// 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.
//
// It reports any write errors back to the caller, Application.SetContentErrorHandler does NOT apply here
// as this is a lower-level method which must be remain as it is.
func (ctx *Context) Write(rawBody []byte) (int, error) {
return ctx.writer.Write(rawBody)
}
@ -3757,38 +3760,20 @@ type ProtoMarshalOptions = protojson.MarshalOptions
// JSON contains the options for the JSON (Context's) Renderer.
type JSON struct {
// http-specific
StreamingJSON bool
StreamingJSON bool `yaml:"StreamingJSON"`
// content-specific
UnescapeHTML bool
Indent string
Prefix string
ASCII bool // if true writes with unicode to ASCII content.
Secure bool // if true then it prepends a "while(1);" when Go slice (to JSON Array) value.
UnescapeHTML bool `yaml:"UnescapeHTML"`
Indent string `yaml:"Indent"`
Prefix string `yaml:"Prefix"`
ASCII bool `yaml:"ASCII"` // if true writes with unicode to ASCII content.
Secure bool `yaml:"Secure"` // if true then it prepends a "while(1);" when Go slice (to JSON Array) value.
// proto.Message specific marshal options.
Proto ProtoMarshalOptions
// Optional context cancelation of encoder when Iris optimizations field is enabled.
// On JSON method this is automatically binded to the request context.
Context stdContext.Context
// ErrorHandler can be optionally registered to fire a customized
// error to the client on JSON write failures.
ErrorHandler ErrorHandler
Proto ProtoMarshalOptions `yaml:"ProtoMarshalOptions"`
}
type (
// ErrorHandler describes a context error handler. As for today this is only used
// to globally or per-party or per-route handle JSON writes error.
ErrorHandler interface {
HandleContextError(ctx *Context, err error)
}
// ErrorHandlerFunc a function shortcut for ErrorHandler interface.
ErrorHandlerFunc func(ctx *Context, err error)
)
func (h ErrorHandlerFunc) HandleContextError(ctx *Context, err error) {
h(ctx, err)
}
// DefaultJSONOptions is the optional settings that are being used
// inside `Context.JSON`.
var DefaultJSONOptions = JSON{}
// IsDefault reports whether this JSON options structure holds the default values.
func (j *JSON) IsDefault() bool {
@ -3799,16 +3784,6 @@ func (j *JSON) IsDefault() bool {
j.ASCII == DefaultJSONOptions.ASCII &&
j.Secure == DefaultJSONOptions.Secure &&
j.Proto == DefaultJSONOptions.Proto
// except context and error handler
}
// GetContext returns the option's Context or the HTTP request's one.
func (j *JSON) GetContext(ctx *Context) stdContext.Context {
if j.Context == nil {
return ctx.request.Context()
}
return j.Context
}
// JSONP contains the options for the JSONP (Context's) Renderer.
@ -3849,32 +3824,64 @@ var (
secureJSONPrefix = []byte("while(1);")
)
func handleJSONResponseValue(w io.Writer, v interface{}, options JSON) (bool, int, error) {
if m, ok := v.(proto.Message); ok {
result, err := options.Proto.Marshal(m)
if err != nil {
return true, 0, err
}
func (ctx *Context) handleSpecialJSONResponseValue(v interface{}, options *JSON) (bool, int, error) {
if ctx.app.ConfigurationReadOnly().GetEnableProtoJSON() {
if m, ok := v.(proto.Message); ok {
protoJSON := ProtoMarshalOptions{}
if options != nil {
protoJSON = options.Proto
}
n, err := w.Write(result)
return true, n, err
result, err := protoJSON.Marshal(m)
if err != nil {
return true, 0, err
}
n, err := ctx.writer.Write(result)
return true, n, err
}
}
if easyObject, ok := v.(easyjson.Marshaler); ok {
jw := jwriter.Writer{NoEscapeHTML: !options.UnescapeHTML}
easyObject.MarshalEasyJSON(&jw)
n, err := jw.DumpTo(w)
return true, n, err
if ctx.app.ConfigurationReadOnly().GetEnableEasyJSON() {
if easyObject, ok := v.(easyjson.Marshaler); ok {
noEscapeHTML := false
if options != nil {
noEscapeHTML = !options.UnescapeHTML
}
jw := jwriter.Writer{NoEscapeHTML: noEscapeHTML}
easyObject.MarshalEasyJSON(&jw)
n, err := jw.DumpTo(ctx.writer)
return true, n, err
}
}
return false, 0, nil
}
// WriteJSON marshals the given interface object and writes the JSON response to the 'writer'.
// Ignores StatusCode and StreamingJSON options.
func WriteJSON(writer io.Writer, v interface{}, options JSON, shouldOptimize bool) (int, error) {
if handled, n, err := handleJSONResponseValue(writer, v, options); handled {
return n, err
func WriteJSON(ctx stdContext.Context, writer io.Writer, v interface{}, options *JSON, shouldOptimize bool) (int, error) {
if options.StreamingJSON {
var err error
if shouldOptimize {
// jsoniterConfig := jsoniter.Config{
// EscapeHTML: !options.UnescapeHTML,
// IndentionStep: 4,
// }.Froze()
// enc := jsoniterConfig.NewEncoder(ctx.writer)
// err = enc.Encode(v)
enc := gojson.NewEncoder(writer)
enc.SetEscapeHTML(!options.UnescapeHTML)
enc.SetIndent(options.Prefix, options.Indent)
err = enc.EncodeContext(ctx, v)
} else {
enc := json.NewEncoder(writer)
enc.SetEscapeHTML(!options.UnescapeHTML)
enc.SetIndent(options.Prefix, options.Indent)
err = enc.Encode(v)
}
return 0, err
}
var (
@ -3899,8 +3906,8 @@ func WriteJSON(writer io.Writer, v interface{}, options JSON, shouldOptimize boo
} else {
if shouldOptimize {
// result, err = jsoniter.ConfigCompatibleWithStandardLibrary.Marshal
if options.Context != nil {
result, err = gojson.MarshalContext(options.Context, v)
if ctx != nil {
result, err = gojson.MarshalContext(ctx, v)
} else {
result, err = gojson.Marshal(v)
}
@ -3939,7 +3946,7 @@ func WriteJSON(writer io.Writer, v interface{}, options JSON, shouldOptimize boo
if options.ASCII {
if len(result) > 0 {
buf := new(bytes.Buffer)
for _, s := range bytesToString(result) {
for _, s := range string(result) {
char := string(s)
if s >= 128 {
char = fmt.Sprintf("\\u%04x", int64(s))
@ -3967,123 +3974,84 @@ func stringToBytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(&s))
}
// DefaultJSONOptions is the optional settings that are being used
// inside `ctx.JSON`.
var DefaultJSONOptions = JSON{}
type (
// ErrorHandler describes a context error handler which applies on
// JSON, JSONP, Protobuf, MsgPack, XML, YAML and Markdown write errors.
//
// An ErrorHandler can be registered once via Application.SetErrorHandler method to override the default behavior.
// The default behavior is to simply send status internal code error
// without a body back to the client.
ErrorHandler interface {
HandleContextError(ctx *Context, err error)
}
// ErrorHandlerFunc a function shortcut for ErrorHandler interface.
ErrorHandlerFunc func(ctx *Context, err error)
)
const jsonOptionsContextKey = "iris.context.json_options"
// SetJSONOptions stores the given JSON options to the handler
// for any next Context.JSON calls. Note that the Context.JSON's
// variadic options have priority over these given options.
//
// Usage Example:
//
// type jsonErrorHandler struct{}
// func (e *jsonErrorHandler) HandleContextError(ctx iris.Context, err error) {
// errors.InvalidArgument.Err(ctx, err)
// }
// ...
// errHandler := new(jsonErrorHandler)
// srv.Use(func(ctx iris.Context) {
// ctx.SetJSONOptions(iris.JSON{
// ErrorHandler: errHandler,
// })
// ctx.Next()
// })
func (ctx *Context) SetJSONOptions(opts JSON) {
ctx.values.Set(jsonOptionsContextKey, opts)
// HandleContextError completes the ErrorHandler interface.
func (h ErrorHandlerFunc) HandleContextError(ctx *Context, err error) {
h(ctx, err)
}
func (ctx *Context) getJSONOptions() (JSON, bool) {
if v := ctx.values.Get(jsonOptionsContextKey); v != nil {
opts, ok := v.(JSON)
return opts, ok
func (ctx *Context) handleContextError(err error) {
if errHandler := ctx.app.GetContextErrorHandler(); errHandler != nil {
errHandler.HandleContextError(ctx, err)
} else {
ctx.StatusCode(http.StatusInternalServerError)
}
return DefaultJSONOptions, false
// keep the error non nil so the caller has control over further actions.
}
// JSON marshals the given interface object and writes the JSON response to the client.
// If the value is a compatible `proto.Message` one
// then it only uses the options.Proto settings to marshal.
// JSON marshals the given "v" value to JSON and writes the response to the client.
// Look the Configuration.EnableProtoJSON/EnableEasyJSON and EnableOptimizations too.
//
// It reports any JSON parser or write errors back to the caller.
// Look the Application.SetContextErrorHandler to override the
// default status code 500 with a custom error response.
//
// It can, optionally, accept the JSON structure which may hold customizations over the
// final JSON response but keep in mind that the caller should NOT modify that JSON options
// value in another goroutine while JSON method is still running.
func (ctx *Context) JSON(v interface{}, opts ...JSON) (n int, err error) {
if n, err = ctx.writeJSON(v, opts...); err != nil {
if opts, ok := ctx.getJSONOptions(); ok {
opts.ErrorHandler.HandleContextError(ctx, err)
} // keep the error so the caller has control over further actions.
var options *JSON
if len(opts) > 0 {
options = &opts[0]
}
if n, err = ctx.writeJSON(v, options); err != nil {
ctx.handleContextError(err)
}
return
}
func (ctx *Context) writeJSON(v interface{}, opts ...JSON) (n int, err error) {
func (ctx *Context) writeJSON(v interface{}, options *JSON) (int, error) {
ctx.ContentType(ContentJSONHeaderValue)
// After content type given and before everything else, try handle proto or easyjson, no matter the performance mode.
if handled, n, err := ctx.handleSpecialJSONResponseValue(v, options); handled {
return n, err
}
shouldOptimize := ctx.shouldOptimize()
options := DefaultJSONOptions
optsLength := len(opts)
if optsLength > 0 {
options = opts[0]
} else {
if opt, ok := ctx.getJSONOptions(); ok {
options = opt
if !options.IsDefault() { // keep the next branch valid when only the Context or/and ErrorHandler are modified.
optsLength = 1
}
}
}
if shouldOptimize && optsLength == 0 { // if no options given and optimizations are enabled.
// try handle proto or easyjson.
if handled, n, err := handleJSONResponseValue(ctx, v, options); handled {
return n, err
}
// as soon as possible, use the fast json marshaler with the http request context.
result, err := gojson.MarshalContext(ctx.request.Context(), v)
if err != nil {
return 0, err
}
return ctx.Write(result)
}
if options.StreamingJSON {
if options == nil {
if shouldOptimize {
// jsoniterConfig := jsoniter.Config{
// EscapeHTML: !options.UnescapeHTML,
// IndentionStep: 4,
// }.Froze()
// enc := jsoniterConfig.NewEncoder(ctx.writer)
// err = enc.Encode(v)
enc := gojson.NewEncoder(ctx.writer)
enc.SetEscapeHTML(!options.UnescapeHTML)
enc.SetIndent(options.Prefix, options.Indent)
err = enc.EncodeContext(options.GetContext(ctx), v)
} else {
enc := json.NewEncoder(ctx.writer)
enc.SetEscapeHTML(!options.UnescapeHTML)
enc.SetIndent(options.Prefix, options.Indent)
err = enc.Encode(v)
// If no options given and optimizations are enabled.
// write using the fast json marshaler with the http request context as soon as possible.
result, err := gojson.MarshalContext(ctx.request.Context(), v)
if err != nil {
return 0, err
}
return ctx.Write(result)
}
if err != nil {
ctx.app.Logger().Debugf("JSON: %v", err)
ctx.StatusCode(http.StatusInternalServerError) // it handles the fallback to normal mode here which also removes any compression headers.
return 0, err
}
return ctx.writer.Written(), err
// Else if no options given neither optimizations are enabled, then safely read the already-initialized object.
options = &DefaultJSONOptions
}
n, err = WriteJSON(ctx.writer, v, options, shouldOptimize)
if err != nil {
ctx.app.Logger().Debugf("JSON: %v", err)
ctx.StatusCode(http.StatusInternalServerError)
return 0, err
}
return n, err
return WriteJSON(ctx, ctx.writer, v, options, shouldOptimize)
}
var finishCallbackB = []byte(");")
@ -4134,24 +4102,23 @@ func WriteJSONP(writer io.Writer, v interface{}, options JSONP, optimize bool) (
// 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) {
// JSONP marshals the given "v" value to JSON and sends the response to the client.
//
// It reports any JSON parser or write errors back to the caller.
// Look the Application.SetContextErrorHandler to override the
// default status code 500 with a custom error response.
func (ctx *Context) JSONP(v interface{}, opts ...JSONP) (n int, err 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.app.Logger().Debugf("JSONP: %v", err)
ctx.StatusCode(http.StatusInternalServerError)
return 0, err
if n, err = WriteJSONP(ctx.writer, v, options, ctx.shouldOptimize()); err != nil {
ctx.handleContextError(err)
}
return n, err
return
}
type xmlMapEntry struct {
@ -4232,29 +4199,28 @@ var DefaultXMLOptions = XML{}
// XML marshals the given interface object and writes the XML response to the client.
// To render maps as XML see the `XMLMap` package-level function.
func (ctx *Context) XML(v interface{}, opts ...XML) (int, error) {
//
// It reports any XML parser or write errors back to the caller.
// Look the Application.SetContextErrorHandler to override the
// default status code 500 with a custom error response.
func (ctx *Context) XML(v interface{}, opts ...XML) (n int, err error) {
options := DefaultXMLOptions
if len(opts) > 0 {
options = opts[0]
}
ctx.ContentType(ContentXMLHeaderValue)
n, err := WriteXML(ctx.writer, v, options, ctx.shouldOptimize())
if err != nil {
ctx.app.Logger().Debugf("XML: %v", err)
ctx.StatusCode(http.StatusInternalServerError)
return 0, err
if n, err = WriteXML(ctx.writer, v, options, ctx.shouldOptimize()); err != nil {
ctx.handleContextError(err)
}
return n, err
return
}
// Problem writes a JSON or XML problem response.
// Order of Problem fields are not always rendered the same.
//
// Behaves exactly like `Context.JSON`
// Behaves exactly like the `Context.JSON` method
// but with default ProblemOptions.JSON indent of " " and
// a response content type of "application/problem+json" instead.
//
@ -4299,11 +4265,12 @@ func (ctx *Context) Problem(v interface{}, opts ...ProblemOptions) (int, error)
}
// WriteMarkdown parses the markdown to html and writes these contents to the writer.
func WriteMarkdown(writer io.Writer, markdownB []byte, options Markdown) (int, error) {
func WriteMarkdown(writer io.Writer, markdownB []byte, options Markdown) (n int, err error) {
buf := blackfriday.Run(markdownB)
if options.Sanitize {
buf = bluemonday.UGCPolicy().SanitizeBytes(buf)
}
return writer.Write(buf)
}
@ -4312,66 +4279,90 @@ func WriteMarkdown(writer io.Writer, markdownB []byte, options Markdown) (int, e
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) {
//
// It reports any Markdown parser or write errors back to the caller.
// Look the Application.SetContextErrorHandler to override the
// default status code 500 with a custom error response.
func (ctx *Context) Markdown(markdownB []byte, opts ...Markdown) (n int, err error) {
options := DefaultMarkdownOptions
if len(opts) > 0 {
options = opts[0]
}
ctx.ContentType(ContentHTMLHeaderValue)
if n, err = WriteMarkdown(ctx.writer, markdownB, options); err != nil {
ctx.handleContextError(err)
}
n, err := WriteMarkdown(ctx.writer, markdownB, options)
return
}
// YAML marshals the given "v" value using the yaml marshaler and writes the result to the client.
//
// It reports any YAML parser or write errors back to the caller.
// Look the Application.SetContextErrorHandler to override the
// default status code 500 with a custom error response.
func (ctx *Context) YAML(v interface{}) (int, error) {
out, err := yaml.Marshal(v)
if err != nil {
ctx.app.Logger().Debugf("Markdown: %v", err)
ctx.StatusCode(http.StatusInternalServerError)
ctx.handleContextError(err)
return 0, err
}
ctx.ContentType(ContentYAMLHeaderValue)
n, err := ctx.Write(out)
if err != nil {
ctx.handleContextError(err)
}
return n, err
}
// YAML marshals the "v" using the yaml marshaler
// and sends the result to the client.
func (ctx *Context) YAML(v interface{}) (int, error) {
out, err := yaml.Marshal(v)
if err != nil {
ctx.app.Logger().Debugf("YAML: %v", err)
ctx.StatusCode(http.StatusInternalServerError)
return 0, err
}
ctx.ContentType(ContentYAMLHeaderValue)
return ctx.Write(out)
}
// TextYAML marshals the "v" using the yaml marshaler
// and renders to the client.
// TextYAML calls the Context.YAML method but with the text/yaml content type instead.
func (ctx *Context) TextYAML(v interface{}) (int, error) {
ctx.contentTypeOnce(ContentYAMLTextHeaderValue, "")
return ctx.YAML(v)
}
// Protobuf parses the "v" of proto Message and renders its result to the client.
// Protobuf marshals the given "v" value of proto Message and writes its result to the client.
//
// It reports any protobuf parser or write errors back to the caller.
// Look the Application.SetContextErrorHandler to override the
// default status code 500 with a custom error response.
func (ctx *Context) Protobuf(v proto.Message) (int, error) {
out, err := proto.Marshal(v)
if err != nil {
ctx.handleContextError(err)
return 0, err
}
ctx.ContentType(ContentProtobufHeaderValue)
return ctx.Write(out)
n, err := ctx.Write(out)
if err != nil {
ctx.handleContextError(err)
}
return n, err
}
// MsgPack parses the "v" of msgpack format and renders its result to the client.
// MsgPack marshals the given "v" value of msgpack format and writes its result to the client.
//
// It reports any message pack or write errors back to the caller.
// Look the Application.SetContextErrorHandler to override the
// default status code 500 with a custom error response.
func (ctx *Context) MsgPack(v interface{}) (int, error) {
out, err := msgpack.Marshal(v)
if err != nil {
return 0, err
ctx.handleContextError(err)
}
ctx.ContentType(ContentMsgPackHeaderValue)
return ctx.Write(out)
n, err := ctx.Write(out)
if err != nil {
ctx.handleContextError(err)
}
return n, err
}
// +-----------------------------------------------------------------------+

24
iris.go
View File

@ -59,6 +59,8 @@ type Application struct {
*router.Router
router.HTTPErrorHandler // if Router is Downgraded this is nil.
ContextPool *context.Pool
// See SetContextErrorHandler, defaults to nil.
contextErrorHandler context.ErrorHandler
// config contains the configuration fields
// all fields defaults to something that is working, developers don't have to set it.
@ -429,6 +431,28 @@ func (app *Application) GetContextPool() *context.Pool {
return app.ContextPool
}
// SetContextErrorHandler can optionally register a handler to handle
// and fire a customized error body to the client on JSON write failures.
//
// ExampleCode:
//
// type contextErrorHandler struct{}
// func (e *contextErrorHandler) HandleContextError(ctx iris.Context, err error) {
// errors.InvalidArgument.Err(ctx, err)
// }
// ...
// app.SetContextErrorHandler(new(contextErrorHandler))
func (app *Application) SetContextErrorHandler(errHandler context.ErrorHandler) *Application {
app.contextErrorHandler = errHandler
return app
}
// GetContextErrorHandler returns the handler which handles errors
// on JSON write failures.
func (app *Application) GetContextErrorHandler() context.ErrorHandler {
return app.contextErrorHandler
}
// ConfigureHost accepts one or more `host#Configuration`, these configurators functions
// can access the host created by `app.Run` or `app.Listen`,
// they're being executed when application is ready to being served to the public.