mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
use Request().Context().Done() channel instead of the ResponseWriter().CloseNotify() one
Former-commit-id: e380a3624c03faada74c9efc24266679c3ff5e2b
This commit is contained in:
parent
ed5172731c
commit
134e2531bf
|
@ -454,7 +454,7 @@ New Context Methods:
|
||||||
|
|
||||||
Breaking Changes:
|
Breaking Changes:
|
||||||
|
|
||||||
|
- `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.
|
||||||
- `iris.CookieEncode` and `CookieDecode` are replaced with the `iris.CookieEncoding`.
|
- `iris.CookieEncode` and `CookieDecode` are replaced with the `iris.CookieEncoding`.
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt" // just an optional helper
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"time" // showcase the delay
|
"time" // showcase the delay
|
||||||
|
|
||||||
"github.com/kataras/iris/v12"
|
"github.com/kataras/iris/v12"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errDone = errors.New("done")
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
|
|
||||||
|
@ -17,15 +19,21 @@ func main() {
|
||||||
i := 0
|
i := 0
|
||||||
ints := []int{1, 2, 3, 5, 7, 9, 11, 13, 15, 17, 23, 29}
|
ints := []int{1, 2, 3, 5, 7, 9, 11, 13, 15, 17, 23, 29}
|
||||||
// Send the response in chunks and wait for half a second between each chunk.
|
// Send the response in chunks and wait for half a second between each chunk.
|
||||||
ctx.StreamWriter(func(w io.Writer) bool {
|
err := ctx.StreamWriter(func(w io.Writer) error {
|
||||||
fmt.Fprintf(w, "Message number %d<br>", ints[i])
|
ctx.Writef("Message number %d<br>", ints[i])
|
||||||
time.Sleep(500 * time.Millisecond) // simulate delay.
|
time.Sleep(500 * time.Millisecond) // simulate delay.
|
||||||
if i == len(ints)-1 {
|
if i == len(ints)-1 {
|
||||||
return false // close and flush
|
return errDone // ends the loop.
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
return true // continue write
|
return nil // continue write
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if err != errDone {
|
||||||
|
// Test it by canceling the request before the stream ends:
|
||||||
|
// [ERRO] $DATETIME stream: context canceled.
|
||||||
|
ctx.Application().Logger().Errorf("stream: %v", err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
type messageNumber struct {
|
type messageNumber struct {
|
||||||
|
@ -33,7 +41,6 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Get("/alternative", func(ctx iris.Context) {
|
app.Get("/alternative", func(ctx iris.Context) {
|
||||||
ctx.ContentType("application/json")
|
|
||||||
ctx.Header("Transfer-Encoding", "chunked")
|
ctx.Header("Transfer-Encoding", "chunked")
|
||||||
i := 0
|
i := 0
|
||||||
ints := []int{1, 2, 3, 5, 7, 9, 11, 13, 15, 17, 23, 29}
|
ints := []int{1, 2, 3, 5, 7, 9, 11, 13, 15, 17, 23, 29}
|
||||||
|
@ -52,3 +59,10 @@ func main() {
|
||||||
|
|
||||||
app.Listen(":8080")
|
app.Listen(":8080")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Look the following methods too:
|
||||||
|
- Context.OnClose(callback)
|
||||||
|
- Context.OnConnectionClose(callback) and
|
||||||
|
- Context.Request().Context().Done()/.Err() too
|
||||||
|
*/
|
||||||
|
|
|
@ -286,15 +286,14 @@ 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)
|
// 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.
|
// when the underlying connection has gone away.
|
||||||
//
|
//
|
||||||
// This mechanism can be used to cancel long operations on the server
|
// This mechanism can be used to cancel long operations on the server
|
||||||
// if the client has disconnected before the response is ready.
|
// if the client has disconnected before the response is ready.
|
||||||
//
|
//
|
||||||
// It depends on the `http#CloseNotify`.
|
// It depends on the Request's Context.Done() channel.
|
||||||
// CloseNotify may wait to notify until Request.Body has been
|
|
||||||
// fully read.
|
|
||||||
//
|
//
|
||||||
// After the main Handler has returned, there is no guarantee
|
// After the main Handler has returned, there is no guarantee
|
||||||
// that the channel receives a value.
|
// that the channel receives a value.
|
||||||
|
@ -303,8 +302,6 @@ type Context interface {
|
||||||
// The "cb" will not fire for sure if the output value is false.
|
// 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.
|
// Note that you can register only one callback for the entire request handler chain/per route.
|
||||||
//
|
|
||||||
// Look the `ResponseWriter#CloseNotifier` for more.
|
|
||||||
OnConnectionClose(fnGoroutine func()) bool
|
OnConnectionClose(fnGoroutine func()) bool
|
||||||
// OnClose registers the callback function "cb" to the underline connection closing event using the `Context#OnConnectionClose`
|
// 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`.
|
// and also in the end of the request handler using the `ResponseWriter#SetBeforeFlush`.
|
||||||
|
@ -775,10 +772,7 @@ type Context interface {
|
||||||
// * if response body is streamed from slow external sources.
|
// * if response body is streamed from slow external sources.
|
||||||
// * if response body must be streamed to the client in chunks.
|
// * if response body must be streamed to the client in chunks.
|
||||||
// (aka `http server push`).
|
// (aka `http server push`).
|
||||||
//
|
StreamWriter(writer func(w io.Writer) error) error
|
||||||
// receives a function which receives the response writer
|
|
||||||
// and returns false when it should stop writing, otherwise true in order to continue
|
|
||||||
StreamWriter(writer func(w io.Writer) bool)
|
|
||||||
|
|
||||||
// +------------------------------------------------------------+
|
// +------------------------------------------------------------+
|
||||||
// | Body Writers with compression |
|
// | Body Writers with compression |
|
||||||
|
@ -1644,15 +1638,14 @@ 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)
|
// 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.
|
// when the underlying connection has gone away.
|
||||||
//
|
//
|
||||||
// This mechanism can be used to cancel long operations on the server
|
// This mechanism can be used to cancel long operations on the server
|
||||||
// if the client has disconnected before the response is ready.
|
// if the client has disconnected before the response is ready.
|
||||||
//
|
//
|
||||||
// It depends on the `http#CloseNotify`.
|
// It depends on the Request's Context.Done() channel.
|
||||||
// CloseNotify may wait to notify until Request.Body has been
|
|
||||||
// fully read.
|
|
||||||
//
|
//
|
||||||
// After the main Handler has returned, there is no guarantee
|
// After the main Handler has returned, there is no guarantee
|
||||||
// that the channel receives a value.
|
// that the channel receives a value.
|
||||||
|
@ -1661,25 +1654,21 @@ func (ctx *context) StopWithProblem(statusCode int, problem Problem) {
|
||||||
// The "cb" will not fire for sure if the output value is false.
|
// 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.
|
// Note that you can register only one callback for the entire request handler chain/per route.
|
||||||
//
|
|
||||||
// Look the `ResponseWriter#CloseNotifier` for more.
|
|
||||||
func (ctx *context) OnConnectionClose(cb func()) bool {
|
func (ctx *context) OnConnectionClose(cb func()) bool {
|
||||||
if cb == nil {
|
if cb == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that `ctx.ResponseWriter().CloseNotify()` can already do the same
|
notifyClose := ctx.Request().Context().Done()
|
||||||
// but it returns a channel which will never fire if it the protocol version is not compatible,
|
if notifyClose == nil {
|
||||||
// here we don't want to allocate an empty channel, just skip it.
|
|
||||||
notifier, ok := ctx.writer.CloseNotifier()
|
|
||||||
if !ok {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
notify := notifier.CloseNotify()
|
|
||||||
go func() {
|
go func() {
|
||||||
<-notify
|
<-notifyClose
|
||||||
cb()
|
cb()
|
||||||
|
// Callers can check the error
|
||||||
|
// through `Context.Request().Context().Err()`.
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -3294,23 +3283,20 @@ func (ctx *context) WriteWithExpiration(body []byte, modtime time.Time) (int, er
|
||||||
// * if response body is streamed from slow external sources.
|
// * if response body is streamed from slow external sources.
|
||||||
// * if response body must be streamed to the client in chunks.
|
// * if response body must be streamed to the client in chunks.
|
||||||
// (aka `http server push`).
|
// (aka `http server push`).
|
||||||
//
|
func (ctx *context) StreamWriter(writer func(w io.Writer) error) error {
|
||||||
// receives a function which receives the response writer
|
cancelCtx := ctx.Request().Context()
|
||||||
// and returns false when it should stop writing, otherwise true in order to continue
|
notifyClosed := cancelCtx.Done()
|
||||||
func (ctx *context) StreamWriter(writer func(w io.Writer) bool) {
|
|
||||||
w := ctx.writer
|
|
||||||
notifyClosed := w.CloseNotify()
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
// response writer forced to close, exit.
|
// response writer forced to close, exit.
|
||||||
case <-notifyClosed:
|
case <-notifyClosed:
|
||||||
return
|
return cancelCtx.Err()
|
||||||
default:
|
default:
|
||||||
shouldContinue := writer(w)
|
if err := writer(ctx.writer); err != nil {
|
||||||
w.Flush()
|
return err
|
||||||
if !shouldContinue {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
ctx.writer.Flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user