mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +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:
|
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.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.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.
|
- `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.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.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.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.ReflectValue() []reflect.Value` stores and returns the `[]reflect.ValueOf(ctx)`
|
||||||
- `Context.Controller() reflect.Value` returns the current MVC Controller value.
|
- `Context.Controller() reflect.Value` returns the current MVC Controller value.
|
||||||
|
|
||||||
Breaking Changes:
|
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.
|
- `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).
|
- 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.
|
- `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()
|
flusher, ok := ctx.ResponseWriter().Flusher()
|
||||||
if !ok {
|
if !ok {
|
||||||
ctx.StatusCode(iris.StatusHTTPVersionNotSupported)
|
ctx.StopWithText(iris.StatusHTTPVersionNotSupported, "Streaming unsupported!")
|
||||||
ctx.WriteString("Streaming unsupported!")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
@ -98,6 +97,13 @@ func (u UnmarshalerFunc) Unmarshal(data []byte, v interface{}) error {
|
||||||
// context.Context is very extensible and developers can override
|
// context.Context is very extensible and developers can override
|
||||||
// its methods if that is actually needed.
|
// its methods if that is actually needed.
|
||||||
type Context interface {
|
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
|
// BeginRequest is executing once for each request
|
||||||
// it should prepare the (new or acquired from pool) context's fields for the new 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.
|
// 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.
|
// 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.
|
// 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.
|
// 2. flushes the response writer's result or fire any error handler.
|
||||||
// 3. releases the response writer.
|
// 3. releases the response writer.
|
||||||
EndRequest()
|
EndRequest()
|
||||||
// Defer executes a handler on this Context right before the request ends.
|
// IsCanceled reports whether the client canceled the request
|
||||||
// The `StopExecution` does not effect the execution of this defer handler.
|
// or the underlying connection has gone.
|
||||||
// The "h" runs before `FireErrorCode` (when response status code is not successful).
|
// Note that it will always return true
|
||||||
Defer(Handler)
|
// 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 returns an http.ResponseWriter compatible response writer, as expected.
|
||||||
ResponseWriter() ResponseWriter
|
ResponseWriter() ResponseWriter
|
||||||
|
@ -286,31 +320,6 @@ type Context interface {
|
||||||
// it will also fire the specified error code handler.
|
// it will also fire the specified error code handler.
|
||||||
StopWithProblem(statusCode int, problem Problem)
|
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 |
|
// | Current "user/request" storage |
|
||||||
// | and share information between the handlers - Values(). |
|
// | and share information between the handlers - Values(). |
|
||||||
|
@ -1251,7 +1260,6 @@ type context struct {
|
||||||
request *http.Request
|
request *http.Request
|
||||||
// the current route registered to this request path.
|
// the current route registered to this request path.
|
||||||
currentRoute RouteReadOnly
|
currentRoute RouteReadOnly
|
||||||
deferFunc Handler
|
|
||||||
|
|
||||||
// the local key-value storage
|
// the local key-value storage
|
||||||
params RequestParams // url named parameters.
|
params RequestParams // url named parameters.
|
||||||
|
@ -1274,6 +1282,30 @@ func NewContext(app Application) Context {
|
||||||
return &context{app: app}
|
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
|
// BeginRequest is executing once for each request
|
||||||
// it should prepare the (new or acquired from pool) context's fields for the new 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.
|
// 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.params.Store = ctx.params.Store[0:0]
|
||||||
ctx.request = r
|
ctx.request = r
|
||||||
ctx.currentHandlerIndex = 0
|
ctx.currentHandlerIndex = 0
|
||||||
ctx.deferFunc = nil
|
|
||||||
ctx.writer = AcquireResponseWriter()
|
ctx.writer = AcquireResponseWriter()
|
||||||
ctx.writer.BeginResponse(w)
|
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.
|
// 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.
|
// 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.
|
// 2. flushes the response writer's result or fire any error handler.
|
||||||
// 3. releases the response writer.
|
// 3. releases the response writer.
|
||||||
func (ctx *context) EndRequest() {
|
func (ctx *context) EndRequest() {
|
||||||
if ctx.deferFunc != nil {
|
|
||||||
ctx.deferFunc(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ctx.app.ConfigurationReadOnly().GetDisableAutoFireStatusCode() &&
|
if !ctx.app.ConfigurationReadOnly().GetDisableAutoFireStatusCode() &&
|
||||||
StatusCodeNotSuccessful(ctx.GetStatusCode()) {
|
StatusCodeNotSuccessful(ctx.GetStatusCode()) {
|
||||||
ctx.app.FireErrorCode(ctx)
|
ctx.app.FireErrorCode(ctx)
|
||||||
|
@ -1316,11 +1343,88 @@ func (ctx *context) EndRequest() {
|
||||||
ctx.writer.EndResponse()
|
ctx.writer.EndResponse()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defer executes a handler on this Context right before the request ends.
|
// IsCanceled reports whether the client canceled the request
|
||||||
// The `StopExecution` does not effect the execution of this defer handler.
|
// or the underlying connection has gone.
|
||||||
// The "h" runs before `FireErrorCode` (when response status code is not successful).
|
// Note that it will always return true
|
||||||
func (ctx *context) Defer(h Handler) {
|
// when called from a goroutine after the request-response lifecycle.
|
||||||
ctx.deferFunc = h
|
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.
|
// 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)
|
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 |
|
// | Current "user/request" storage |
|
||||||
// | and share information between the handlers - Values(). |
|
// | and share information between the handlers - Values(). |
|
||||||
|
|
Loading…
Reference in New Issue
Block a user