mirror of
https://github.com/kataras/iris.git
synced 2025-01-22 18:21:03 +01:00
new Application.SetContextErrorHandler method
This commit is contained in:
parent
73dfabf412
commit
ae828d8db9
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
117
configuration.go
117
configuration.go
|
@ -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{}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
24
iris.go
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue
Block a user