mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
add Context.Clone and change the input argument of Context.OnClose and OnCloseConnection ( the un-released Defer has been removed, OnClose can do its job)
Former-commit-id: 7b606e285f4b9de24338ea96d482cf1f7c4907a3
This commit is contained in:
parent
2d9485326b
commit
85fc0f5dab
|
@ -425,6 +425,8 @@ New Package-level Variables:
|
|||
|
||||
New Context Methods:
|
||||
|
||||
- `Context.Clone() Context` returns a copy of the Context.
|
||||
- `Context.IsCanceled() bool` reports whether the request has been canceled by the client.
|
||||
- `Context.IsSSL() bool` reports whether the request is under HTTPS SSL (New `Configuration.SSLProxyHeaders` and `HostProxyHeaders` fields too).
|
||||
- `Context.GzipReader(enable bool)` method and `iris.GzipReader` middleware to enable future request read body calls to decompress data using gzip, [example](_examples/request-body/read-gzip).
|
||||
- `Context.RegisterDependency(v interface{})` and `Context.UnregisterDependency(typ reflect.Type)` to register/remove struct dependencies on serve-time through a middleware.
|
||||
|
@ -448,12 +450,12 @@ New Context Methods:
|
|||
- `Context.ReadJSONProtobuf(ptr, ...options)` binds JSON request body to a proto message
|
||||
- `Context.ReadMsgPack(ptr)` binds request body of a msgpack format to a struct
|
||||
- `Context.ReadBody(ptr)` binds the request body to the "ptr" depending on the request's Method and Content-Type
|
||||
- `Context.Defer(Handler)` works like `Party.Done` but for the request life-cycle instead
|
||||
- `Context.ReflectValue() []reflect.Value` stores and returns the `[]reflect.ValueOf(ctx)`
|
||||
- `Context.Controller() reflect.Value` returns the current MVC Controller value.
|
||||
|
||||
Breaking Changes:
|
||||
|
||||
- `Context.OnClose` and `Context.OnCloseConnection` now both accept an `iris.Handler` instead of a simple `func()` as their callback.
|
||||
- `Context.StreamWriter(writer func(w io.Writer) bool)` changed to `StreamWriter(writer func(w io.Writer) error) error` and it's now the `Context.Request().Context().Done()` channel that is used to receive any close connection/manual cancel signals, instead of the deprecated `ResponseWriter().CloseNotify()` one. Same for the `Context.OnClose` and `Context.OnCloseConnection` methods.
|
||||
- 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).
|
||||
- `Context.String()` (rarely used by end-developers) it does not return a unique string anymore, to achieve the old representation you must call the new `Context.SetID` method first.
|
||||
|
|
|
@ -76,8 +76,7 @@ func (b *Broker) ServeHTTP(ctx iris.Context) {
|
|||
|
||||
flusher, ok := ctx.ResponseWriter().Flusher()
|
||||
if !ok {
|
||||
ctx.StatusCode(iris.StatusHTTPVersionNotSupported)
|
||||
ctx.WriteString("Streaming unsupported!")
|
||||
ctx.StopWithText(iris.StatusHTTPVersionNotSupported, "Streaming unsupported!")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ import (
|
|||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
|
@ -98,6 +97,13 @@ func (u UnmarshalerFunc) Unmarshal(data []byte, v interface{}) error {
|
|||
// context.Context is very extensible and developers can override
|
||||
// its methods if that is actually needed.
|
||||
type Context interface {
|
||||
// Clone returns a copy of the context that
|
||||
// can be safely used outside the request's scope.
|
||||
// Note that if the request-response lifecycle terminated
|
||||
// or request canceled by the client (can be checked by `ctx.IsCanceled()`)
|
||||
// then the response writer is totally useless.
|
||||
// The http.Request pointer value is shared.
|
||||
Clone() Context
|
||||
// BeginRequest is executing once for each request
|
||||
// it should prepare the (new or acquired from pool) context's fields for the new request.
|
||||
// Do NOT call it manually. Framework calls it automatically.
|
||||
|
@ -112,14 +118,42 @@ type Context interface {
|
|||
// 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.
|
||||
//
|
||||
// 1. executes the Defer function (if any).
|
||||
// 1. executes the OnClose function (if any).
|
||||
// 2. flushes the response writer's result or fire any error handler.
|
||||
// 3. releases the response writer.
|
||||
EndRequest()
|
||||
// Defer executes a handler on this Context right before the request ends.
|
||||
// The `StopExecution` does not effect the execution of this defer handler.
|
||||
// The "h" runs before `FireErrorCode` (when response status code is not successful).
|
||||
Defer(Handler)
|
||||
// IsCanceled reports whether the client canceled the request
|
||||
// or the underlying connection has gone.
|
||||
// Note that it will always return true
|
||||
// when called from a goroutine after the request-response lifecycle.
|
||||
IsCanceled() bool
|
||||
// OnConnectionClose registers the "cb" Handler
|
||||
// which will be fired on its on goroutine on a cloned Context
|
||||
// when the underlying connection has gone away.
|
||||
//
|
||||
// The code inside the given callback is running on its own routine,
|
||||
// as explained above, therefore the callback should NOT
|
||||
// try to access to handler's Context response writer.
|
||||
//
|
||||
// This mechanism can be used to cancel long operations on the server
|
||||
// if the client has disconnected before the response is ready.
|
||||
//
|
||||
// It depends on the Request's Context.Done() channel.
|
||||
//
|
||||
// Finally, it reports whether the protocol supports pipelines (HTTP/1.1 with pipelines disabled is not supported).
|
||||
// The "cb" will not fire for sure if the output value is false.
|
||||
//
|
||||
// Note that you can register only one callback per route.
|
||||
//
|
||||
// See `OnClose` too.
|
||||
OnConnectionClose(Handler) bool
|
||||
// OnClose registers a callback which
|
||||
// will be fired when the underlying connection has gone away(request canceled)
|
||||
// on its own goroutine or in the end of the request-response lifecylce
|
||||
// on the handler's routine itself (Context access).
|
||||
//
|
||||
// See `OnConnectionClose` too.
|
||||
OnClose(Handler)
|
||||
|
||||
// ResponseWriter returns an http.ResponseWriter compatible response writer, as expected.
|
||||
ResponseWriter() ResponseWriter
|
||||
|
@ -286,31 +320,6 @@ type Context interface {
|
|||
// it will also fire the specified error code handler.
|
||||
StopWithProblem(statusCode int, problem Problem)
|
||||
|
||||
// OnConnectionClose registers the "cb" function which will fire
|
||||
// (on its own goroutine, no need to be registered goroutine by the end-dev)
|
||||
// when the underlying connection has gone away.
|
||||
//
|
||||
// This mechanism can be used to cancel long operations on the server
|
||||
// if the client has disconnected before the response is ready.
|
||||
//
|
||||
// It depends on the Request's Context.Done() channel.
|
||||
//
|
||||
// After the main Handler has returned, there is no guarantee
|
||||
// that the channel receives a value.
|
||||
//
|
||||
// Finally, it reports whether the protocol supports pipelines (HTTP/1.1 with pipelines disabled is not supported).
|
||||
// The "cb" will not fire for sure if the output value is false.
|
||||
//
|
||||
// Note that you can register only one callback for the entire request handler chain/per route.
|
||||
OnConnectionClose(fnGoroutine func()) bool
|
||||
// OnClose registers the callback function "cb" to the underline connection closing event using the `Context#OnConnectionClose`
|
||||
// and also in the end of the request handler using the `ResponseWriter#SetBeforeFlush`.
|
||||
// Note that you can register only one callback for the entire request handler chain/per route.
|
||||
// Note that the "cb" will only be called once.
|
||||
//
|
||||
// Look the `Context#OnConnectionClose` and `ResponseWriter#SetBeforeFlush` for more.
|
||||
OnClose(cb func())
|
||||
|
||||
// +------------------------------------------------------------+
|
||||
// | Current "user/request" storage |
|
||||
// | and share information between the handlers - Values(). |
|
||||
|
@ -1251,7 +1260,6 @@ type context struct {
|
|||
request *http.Request
|
||||
// the current route registered to this request path.
|
||||
currentRoute RouteReadOnly
|
||||
deferFunc Handler
|
||||
|
||||
// the local key-value storage
|
||||
params RequestParams // url named parameters.
|
||||
|
@ -1274,6 +1282,30 @@ func NewContext(app Application) Context {
|
|||
return &context{app: app}
|
||||
}
|
||||
|
||||
// Clone returns a copy of the context that
|
||||
// can be safely used outside the request's scope.
|
||||
// Note that if the request-response lifecycle terminated
|
||||
// or request canceled by the client (can be checked by `ctx.IsCanceled()`)
|
||||
// then the response writer is totally useless.
|
||||
// The http.Request pointer value is shared.
|
||||
func (ctx *context) Clone() Context {
|
||||
valuesCopy := make(memstore.Store, len(ctx.values))
|
||||
copy(valuesCopy, ctx.values)
|
||||
|
||||
paramsCopy := make(memstore.Store, len(ctx.params.Store))
|
||||
copy(paramsCopy, ctx.params.Store)
|
||||
|
||||
return &context{
|
||||
app: ctx.app,
|
||||
values: valuesCopy,
|
||||
params: RequestParams{Store: paramsCopy},
|
||||
writer: ctx.writer.Clone(),
|
||||
request: ctx.request,
|
||||
currentHandlerIndex: stopExecutionIndex,
|
||||
currentRoute: ctx.currentRoute,
|
||||
}
|
||||
}
|
||||
|
||||
// BeginRequest is executing once for each request
|
||||
// it should prepare the (new or acquired from pool) context's fields for the new request.
|
||||
// Do NOT call it manually. Framework calls it automatically.
|
||||
|
@ -1291,7 +1323,6 @@ func (ctx *context) BeginRequest(w http.ResponseWriter, r *http.Request) {
|
|||
ctx.params.Store = ctx.params.Store[0:0]
|
||||
ctx.request = r
|
||||
ctx.currentHandlerIndex = 0
|
||||
ctx.deferFunc = nil
|
||||
ctx.writer = AcquireResponseWriter()
|
||||
ctx.writer.BeginResponse(w)
|
||||
}
|
||||
|
@ -1299,14 +1330,10 @@ func (ctx *context) BeginRequest(w http.ResponseWriter, r *http.Request) {
|
|||
// 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.
|
||||
//
|
||||
// 1. executes the Defer function (if any).
|
||||
// 1. executes the OnClose function (if any).
|
||||
// 2. flushes the response writer's result or fire any error handler.
|
||||
// 3. releases the response writer.
|
||||
func (ctx *context) EndRequest() {
|
||||
if ctx.deferFunc != nil {
|
||||
ctx.deferFunc(ctx)
|
||||
}
|
||||
|
||||
if !ctx.app.ConfigurationReadOnly().GetDisableAutoFireStatusCode() &&
|
||||
StatusCodeNotSuccessful(ctx.GetStatusCode()) {
|
||||
ctx.app.FireErrorCode(ctx)
|
||||
|
@ -1316,11 +1343,88 @@ func (ctx *context) EndRequest() {
|
|||
ctx.writer.EndResponse()
|
||||
}
|
||||
|
||||
// Defer executes a handler on this Context right before the request ends.
|
||||
// The `StopExecution` does not effect the execution of this defer handler.
|
||||
// The "h" runs before `FireErrorCode` (when response status code is not successful).
|
||||
func (ctx *context) Defer(h Handler) {
|
||||
ctx.deferFunc = h
|
||||
// IsCanceled reports whether the client canceled the request
|
||||
// or the underlying connection has gone.
|
||||
// Note that it will always return true
|
||||
// when called from a goroutine after the request-response lifecycle.
|
||||
func (ctx *context) IsCanceled() bool {
|
||||
if reqCtx := ctx.request.Context(); reqCtx != nil {
|
||||
err := reqCtx.Err()
|
||||
if errors.Is(err, stdContext.Canceled) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// OnConnectionClose registers the "cb" Handler
|
||||
// which will be fired on its on goroutine on a cloned Context
|
||||
// when the underlying connection has gone away.
|
||||
//
|
||||
// The code inside the given callback is running on its own routine,
|
||||
// as explained above, therefore the callback should NOT
|
||||
// try to access to handler's Context response writer.
|
||||
//
|
||||
// This mechanism can be used to cancel long operations on the server
|
||||
// if the client has disconnected before the response is ready.
|
||||
//
|
||||
// It depends on the Request's Context.Done() channel.
|
||||
//
|
||||
// Finally, it reports whether the protocol supports pipelines (HTTP/1.1 with pipelines disabled is not supported).
|
||||
// The "cb" will not fire for sure if the output value is false.
|
||||
//
|
||||
// Note that you can register only one callback per route.
|
||||
//
|
||||
// See `OnClose` too.
|
||||
func (ctx *context) OnConnectionClose(cb Handler) bool {
|
||||
if cb == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
reqCtx := ctx.Request().Context()
|
||||
if reqCtx == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
notifyClose := reqCtx.Done()
|
||||
if notifyClose == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-notifyClose
|
||||
// Note(@kataras): No need to clone if not canceled,
|
||||
// EndRequest will be called on the end of the handler chain,
|
||||
// no matter the cancelation.
|
||||
// therefore the context will still be there.
|
||||
cb(ctx.Clone())
|
||||
}()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// OnClose registers a callback which
|
||||
// will be fired when the underlying connection has gone away(request canceled)
|
||||
// on its own goroutine or in the end of the request-response lifecylce
|
||||
// on the handler's routine itself (Context access).
|
||||
//
|
||||
// See `OnConnectionClose` too.
|
||||
func (ctx *context) OnClose(cb Handler) {
|
||||
if cb == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.OnConnectionClose(cb)
|
||||
|
||||
fn := func() {
|
||||
if !ctx.IsCanceled() {
|
||||
// If the callback not fired by OnConnectionClose already.
|
||||
cb(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.writer.SetBeforeFlush(fn)
|
||||
}
|
||||
|
||||
// ResponseWriter returns an http.ResponseWriter compatible response writer, as expected.
|
||||
|
@ -1638,81 +1742,6 @@ func (ctx *context) StopWithProblem(statusCode int, problem Problem) {
|
|||
ctx.Problem(problem)
|
||||
}
|
||||
|
||||
// OnConnectionClose registers the "cb" function which will fire
|
||||
// (on its own goroutine, no need to be registered goroutine by the end-dev)
|
||||
// when the underlying connection has gone away.
|
||||
//
|
||||
// This mechanism can be used to cancel long operations on the server
|
||||
// if the client has disconnected before the response is ready.
|
||||
//
|
||||
// It depends on the Request's Context.Done() channel.
|
||||
//
|
||||
// After the main Handler has returned, there is no guarantee
|
||||
// that the channel receives a value.
|
||||
//
|
||||
// Finally, it reports whether the protocol supports pipelines (HTTP/1.1 with pipelines disabled is not supported).
|
||||
// The "cb" will not fire for sure if the output value is false.
|
||||
//
|
||||
// Note that you can register only one callback for the entire request handler chain/per route.
|
||||
func (ctx *context) OnConnectionClose(cb func()) bool {
|
||||
if cb == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
notifyClose := ctx.Request().Context().Done()
|
||||
if notifyClose == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-notifyClose
|
||||
cb()
|
||||
// Callers can check the error
|
||||
// through `Context.Request().Context().Err()`.
|
||||
}()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// OnClose registers the callback function "cb" to the underline connection closing event using the `Context#OnConnectionClose`
|
||||
// and also in the end of the request handler using the `ResponseWriter#SetBeforeFlush`.
|
||||
// Note that you can register only one callback for the entire request handler chain/per route.
|
||||
//
|
||||
// Note that the "cb" will only be called once.
|
||||
//
|
||||
// Look the `Context#OnConnectionClose` and `ResponseWriter#SetBeforeFlush` for more.
|
||||
func (ctx *context) OnClose(cb func()) {
|
||||
if cb == nil {
|
||||
return
|
||||
}
|
||||
|
||||
once := new(sync.Once)
|
||||
|
||||
callOnce := func() {
|
||||
once.Do(cb)
|
||||
}
|
||||
|
||||
// Register the on underline connection close handler first.
|
||||
ctx.OnConnectionClose(callOnce)
|
||||
|
||||
// Author's notes:
|
||||
// This is fired on `ctx.ResponseWriter().FlushResponse()` which is fired by the framework automatically, internally, on the end of request handler(s),
|
||||
// it is not fired on the underline streaming function of the writer: `ctx.ResponseWriter().Flush()` (which can be fired more than one if streaming is supported by the client).
|
||||
// The `FlushResponse` is called only once, so add the "cb" here, no need to add done request handlers each time `OnClose` is called by the end-dev.
|
||||
//
|
||||
// Don't allow more than one because we don't allow that on `OnConnectionClose` too:
|
||||
// old := ctx.writer.GetBeforeFlush()
|
||||
// if old != nil {
|
||||
// ctx.writer.SetBeforeFlush(func() {
|
||||
// old()
|
||||
// cb()
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
|
||||
ctx.writer.SetBeforeFlush(callOnce)
|
||||
}
|
||||
|
||||
// +------------------------------------------------------------+
|
||||
// | Current "user/request" storage |
|
||||
// | and share information between the handlers - Values(). |
|
||||
|
|
Loading…
Reference in New Issue
Block a user