mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
Add 'context.OnConnectionClose(callbackFn) bool' and 'context.OnClose(callbackFn)' and give a use case example. More on this path later on, stay tuned.
Former-commit-id: dc6580f072d076b8cb204a681e45905210981153
This commit is contained in:
parent
d98da25ffb
commit
5d9ded37c4
|
@ -6,6 +6,57 @@ This folder provides easy to understand code snippets on how to get started with
|
|||
|
||||
It doesn't always contain the "best ways" but it does cover each important feature that will make you so excited to GO with iris!
|
||||
|
||||
## Running the examples
|
||||
|
||||
1. Install the Go Programming Language, version 1.9+ from [here](https://golang.org/dl).
|
||||
2. Install Iris: `go get -u github.com/kataras/iris`
|
||||
3. Install any external packages that required by the examples
|
||||
|
||||
<details>
|
||||
<summary>External packages</summary>
|
||||
|
||||
```bash
|
||||
cd _examples && go get ./...
|
||||
# or
|
||||
go get github.com/iris-contrib/middleware/...
|
||||
go get github.com/betacraft/yaag/irisyaag
|
||||
go get github.com/markbates/goth/...
|
||||
go get github.com/getsentry/raven-go/...
|
||||
go get github.com/casbin/casbin
|
||||
go get github.com/markbates/goth/...
|
||||
go get github.com/aws/aws-sdk-go/...
|
||||
go get github.com/getsentry/raven-go/...
|
||||
go get github.com/casbin/casbin
|
||||
go get github.com/aws/aws-sdk-go/...
|
||||
go get github.com/prometheus/client_golang/...
|
||||
go get github.com/didip/tollbooth
|
||||
go get github.com/valyala/quicktemplate
|
||||
go get github.com/shiyanhui/hero
|
||||
go get github.com/go-xorm/xorm
|
||||
go get github.com/nfnt/resize
|
||||
go get github.com/prometheus/client_golang/...
|
||||
go get github.com/didip/tollbooth
|
||||
go get github.com/valyala/quicktemplate
|
||||
go get github.com/shiyanhui/hero
|
||||
go get github.com/go-xorm/xorm
|
||||
go get github.com/nfnt/resize
|
||||
go get github.com/dgrijalva/jwt-go
|
||||
go get github.com/newrelic/go-agent
|
||||
go get github.com/valyala/tcplisten
|
||||
go get github.com/kataras/bindata/cmd/bindata
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
And execute
|
||||
|
||||
```sh
|
||||
$ cd $GOPATH/src/github.com/kataras/iris/_examples/overview
|
||||
$ go run main.go
|
||||
```
|
||||
|
||||
> Test the examples by opening a terminal window and execute: `GOCACHE=off && cd _examples && go test -v ./...`
|
||||
|
||||
### Overview
|
||||
|
||||
- [Hello world!](hello-world/main.go)
|
||||
|
|
|
@ -54,7 +54,7 @@ func (r resource) loadFromBase(dir string) string {
|
|||
result := string(b)
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
result = strings.Replace(result, "\n", "\r\n", -1)
|
||||
// result = strings.Replace(result, "\n", "\r\n", -1)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// Code generated by bindata. DO NOT EDIT.
|
||||
// sources:
|
||||
// assets\css\bootstrap.min.css
|
||||
// assets\favicon.ico
|
||||
// assets\js\jquery-2.1.1.js
|
||||
// assets/css/bootstrap.min.css
|
||||
// assets/favicon.ico
|
||||
// assets/js/jquery-2.1.1.js
|
||||
|
||||
package main
|
||||
|
||||
|
@ -55,7 +55,7 @@ func (fi gzipBindataFileInfo) Sys() interface{} {
|
|||
return nil
|
||||
}
|
||||
|
||||
var _gzipBindataAssetscssbootstrapmincss = []byte(
|
||||
var _gzipBindataAssetsCssBootstrapmincss = []byte(
|
||||
"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\xbd\xef\x8f\xe3\x38\x92\x20\xfa\xb9\x1a\xe8\xff\x41\x5b\x8d\x41\x57" +
|
||||
"\x4d\x59\x2e\xf9\x77\xda\x89\xce\xb7\xfb\xe6\x0e\xb7\x03\xdc\xec\x97\x9b\x0f\x07\xf4\xf4\x7b\xa0\x25\xda\xd6\x94" +
|
||||
"\x2c\x69\x24\x39\x2b\xb3\xfb\xf6\xfe\xf6\x83\xf8\x4b\x41\x32\x48\xd1\x4e\x77\xcf\xdc\x62\xb7\xee\x7a\x9c\x62\x44" +
|
||||
|
@ -806,14 +806,14 @@ var _gzipBindataAssetscssbootstrapmincss = []byte(
|
|||
"\x6f\x60\x83\x31\xfd\x46\x3e\xae\xb6\x0e\x28\xfe\xfd\xe4\x72\x0f\x83\x02\x88\x8b\x01\x82\xf5\x3c\x3e\x46\xfe\x4f" +
|
||||
"\x00\x00\x00\xff\xff\x47\x37\xb0\x07\xc9\x28\x02\x00")
|
||||
|
||||
func gzipBindataAssetscssbootstrapmincss() (*gzipAsset, error) {
|
||||
bytes := _gzipBindataAssetscssbootstrapmincss
|
||||
func gzipBindataAssetsCssBootstrapmincss() (*gzipAsset, error) {
|
||||
bytes := _gzipBindataAssetsCssBootstrapmincss
|
||||
info := gzipBindataFileInfo{
|
||||
name: "assets/css/bootstrap.min.css",
|
||||
size: 141513,
|
||||
md5checksum: "",
|
||||
mode: os.FileMode(438),
|
||||
modTime: time.Unix(1520997241, 0),
|
||||
mode: os.FileMode(511),
|
||||
modTime: time.Unix(1521004692, 0),
|
||||
}
|
||||
|
||||
a := &gzipAsset{bytes: bytes, info: info}
|
||||
|
@ -821,7 +821,7 @@ func gzipBindataAssetscssbootstrapmincss() (*gzipAsset, error) {
|
|||
return a, nil
|
||||
}
|
||||
|
||||
var _gzipBindataAssetsfaviconico = []byte(
|
||||
var _gzipBindataAssetsFaviconico = []byte(
|
||||
"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5a\x0b\x70\x55\xc7\x79\xde\x7b\xce\x45\x12\xb2\x90\xc4\xc3\xe6\x61" +
|
||||
"\x3b\x90\xf8\x31\xc4\x19\x6c\x32\xe3\xc4\x34\xe3\xc6\x34\x6d\xed\xd4\x69\x62\x32\x49\x9a\xa6\x75\xea\x36\x33\xee" +
|
||||
"\xd8\x9e\xb4\x75\xdc\x7a\xa6\xc5\x31\x02\xa6\xd3\x84\x47\x28\x6f\x3b\xc6\x3c\xcc\xeb\x9e\xbd\x08\x21\x04\x92\x28" +
|
||||
|
@ -967,14 +967,14 @@ var _gzipBindataAssetsfaviconico = []byte(
|
|||
"\x0c\x24\x28\xcd\x41\x65\xd4\x2a\xc1\x2e\x17\x0e\x2a\x5b\xac\xb2\xc7\x2a\x4d\xab\x4c\xa1\xdf\xff\x0f\x00\x00\xff" +
|
||||
"\xff\xc6\xb9\x24\x2f\xee\x3a\x00\x00")
|
||||
|
||||
func gzipBindataAssetsfaviconico() (*gzipAsset, error) {
|
||||
bytes := _gzipBindataAssetsfaviconico
|
||||
func gzipBindataAssetsFaviconico() (*gzipAsset, error) {
|
||||
bytes := _gzipBindataAssetsFaviconico
|
||||
info := gzipBindataFileInfo{
|
||||
name: "assets/favicon.ico",
|
||||
size: 15086,
|
||||
md5checksum: "",
|
||||
mode: os.FileMode(438),
|
||||
modTime: time.Unix(1520997241, 0),
|
||||
mode: os.FileMode(511),
|
||||
modTime: time.Unix(1521004692, 0),
|
||||
}
|
||||
|
||||
a := &gzipAsset{bytes: bytes, info: info}
|
||||
|
@ -982,7 +982,7 @@ func gzipBindataAssetsfaviconico() (*gzipAsset, error) {
|
|||
return a, nil
|
||||
}
|
||||
|
||||
var _gzipBindataAssetsjsjquery211js = []byte(
|
||||
var _gzipBindataAssetsJsJquery211js = []byte(
|
||||
"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\xfd\x7b\x77\xdb\x46\x96\x28\x8a\xff\x2d\xaf\xe5\xef\x50\xa2\x73\x64" +
|
||||
"\xd0\xe2\x43\xb2\x93\x74\x42\x99\xd6\xcf\xb1\x9d\x1e\x9d\x5f\xec\xb8\x23\x67\x32\xf7\x4a\xca\xa4\x48\x14\xc5\xb2" +
|
||||
"\x41\x80\x41\x81\x92\xd8\xa1\xfa\xb3\xdf\xb5\x1f\xf5\x02\x40\xd9\xee\x4e\xcf\x39\x3d\x6b\x62\x11\x28\xd4\x63\xd7" +
|
||||
|
@ -3634,14 +3634,14 @@ var _gzipBindataAssetsjsjquery211js = []byte(
|
|||
"\xfa\x6a\x18\xef\xb5\xaa\xc6\xb4\x04\x30\x01\x3d\xd6\x92\x74\x16\xc4\xcc\x86\x81\x87\x9d\x94\xce\x89\xbb\xe5\xdc" +
|
||||
"\x66\xfb\xff\x03\x00\x00\xff\xff\x1d\x30\x27\xdd\x1d\xea\x03\x00")
|
||||
|
||||
func gzipBindataAssetsjsjquery211js() (*gzipAsset, error) {
|
||||
bytes := _gzipBindataAssetsjsjquery211js
|
||||
func gzipBindataAssetsJsJquery211js() (*gzipAsset, error) {
|
||||
bytes := _gzipBindataAssetsJsJquery211js
|
||||
info := gzipBindataFileInfo{
|
||||
name: "assets/js/jquery-2.1.1.js",
|
||||
size: 256541,
|
||||
md5checksum: "",
|
||||
mode: os.FileMode(438),
|
||||
modTime: time.Unix(1520997241, 0),
|
||||
mode: os.FileMode(511),
|
||||
modTime: time.Unix(1521004692, 0),
|
||||
}
|
||||
|
||||
a := &gzipAsset{bytes: bytes, info: info}
|
||||
|
@ -3706,9 +3706,9 @@ func GzipAssetNames() []string {
|
|||
// _gzipbindata is a table, holding each asset generator, mapped to its name.
|
||||
//
|
||||
var _gzipbindata = map[string]func() (*gzipAsset, error){
|
||||
"assets/css/bootstrap.min.css": gzipBindataAssetscssbootstrapmincss,
|
||||
"assets/favicon.ico": gzipBindataAssetsfaviconico,
|
||||
"assets/js/jquery-2.1.1.js": gzipBindataAssetsjsjquery211js,
|
||||
"assets/css/bootstrap.min.css": gzipBindataAssetsCssBootstrapmincss,
|
||||
"assets/favicon.ico": gzipBindataAssetsFaviconico,
|
||||
"assets/js/jquery-2.1.1.js": gzipBindataAssetsJsJquery211js,
|
||||
}
|
||||
|
||||
|
||||
|
@ -3764,11 +3764,11 @@ type gzipBintree struct {
|
|||
var _gzipbintree = &gzipBintree{Func: nil, Children: map[string]*gzipBintree{
|
||||
"assets": {Func: nil, Children: map[string]*gzipBintree{
|
||||
"css": {Func: nil, Children: map[string]*gzipBintree{
|
||||
"bootstrap.min.css": {Func: gzipBindataAssetscssbootstrapmincss, Children: map[string]*gzipBintree{}},
|
||||
"bootstrap.min.css": {Func: gzipBindataAssetsCssBootstrapmincss, Children: map[string]*gzipBintree{}},
|
||||
}},
|
||||
"favicon.ico": {Func: gzipBindataAssetsfaviconico, Children: map[string]*gzipBintree{}},
|
||||
"favicon.ico": {Func: gzipBindataAssetsFaviconico, Children: map[string]*gzipBintree{}},
|
||||
"js": {Func: nil, Children: map[string]*gzipBintree{
|
||||
"jquery-2.1.1.js": {Func: gzipBindataAssetsjsjquery211js, Children: map[string]*gzipBintree{}},
|
||||
"jquery-2.1.1.js": {Func: gzipBindataAssetsJsJquery211js, Children: map[string]*gzipBintree{}},
|
||||
}},
|
||||
}},
|
||||
}}
|
||||
|
|
|
@ -55,7 +55,7 @@ func (r resource) loadFromBase(dir string) string {
|
|||
result := string(b)
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
result = strings.Replace(result, "\n", "\r\n", -1)
|
||||
// result = strings.Replace(result, "\n", "\r\n", -1)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
@ -98,8 +98,11 @@ func TestEmbeddingGzipFilesIntoApp(t *testing.T) {
|
|||
}
|
||||
buf := new(bytes.Buffer)
|
||||
reader.WriteTo(buf)
|
||||
if rawContents != buf.String() {
|
||||
t.Fatalf("[%d] of '%s': expected body:\n%s but got:\n%s", i, url, rawContents, buf.String())
|
||||
if expected, got := rawContents, buf.String(); expected != got {
|
||||
// t.Fatalf("[%d] of '%s': expected body:\n%s but got:\n%s", i, url, expected, got)
|
||||
// let's reduce the output here...
|
||||
// they are big files, no need to check for length here.
|
||||
t.Fatalf("[%d] %s, expected body to look like: '%s...%s' but got '%s...%s'", i, url, expected[:40], expected[len(rawContents)-40:], got[:40], got[len(got)-40:])
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ func (r resource) loadFromBase(dir string) string {
|
|||
}
|
||||
result := string(b)
|
||||
if runtime.GOOS != "windows" {
|
||||
result = strings.Replace(result, "\n", "\r\n", -1)
|
||||
// result = strings.Replace(result, "\n", "\r\n", -1)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -6,11 +6,9 @@ package main
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/golog"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
// Note:
|
||||
// For some reason the latest vscode-go language extension does not provide enough intelligence (parameters documentation and go to definition features)
|
||||
|
@ -80,10 +78,10 @@ func (b *Broker) listen() {
|
|||
|
||||
func (b *Broker) ServeHTTP(ctx context.Context) {
|
||||
// Make sure that the writer supports flushing.
|
||||
//
|
||||
flusher, ok := ctx.ResponseWriter().(http.Flusher)
|
||||
|
||||
flusher, ok := ctx.ResponseWriter().Flusher()
|
||||
if !ok {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.StatusCode(iris.StatusHTTPVersionNotSupported)
|
||||
ctx.WriteString("Streaming unsupported!")
|
||||
return
|
||||
}
|
||||
|
@ -102,19 +100,27 @@ func (b *Broker) ServeHTTP(ctx context.Context) {
|
|||
// Signal the broker that we have a new connection.
|
||||
b.newClients <- messageChan
|
||||
|
||||
// Remove this client from the map of connected clients
|
||||
// when this handler exits.
|
||||
defer func() {
|
||||
b.closingClients <- messageChan
|
||||
}()
|
||||
// Listen to connection close or when the entire request handler chain exits and un-register messageChan.
|
||||
// using the `ctx.ResponseWriter().CloseNotifier()` and `defer` for this single handler of the route:
|
||||
/*
|
||||
notifier, ok := ctx.ResponseWriter().CloseNotifier()
|
||||
if ok {
|
||||
go func() {
|
||||
<-notifier.CloseNotify()
|
||||
b.closingClients <- messageChan
|
||||
}()
|
||||
}
|
||||
|
||||
// Listen to connection close and un-register messageChan.
|
||||
notify := ctx.ResponseWriter().(http.CloseNotifier).CloseNotify()
|
||||
|
||||
go func() {
|
||||
<-notify
|
||||
defer func() {
|
||||
b.closingClients <- messageChan
|
||||
}()
|
||||
*/
|
||||
// or by using the `ctx.OnClose`, which will take care all of the above for you:
|
||||
ctx.OnClose(func() {
|
||||
// Remove this client from the map of connected clients
|
||||
// when this handler exits.
|
||||
b.closingClients <- messageChan
|
||||
}()
|
||||
})
|
||||
|
||||
// block waiting for messages broadcast on this connection's messageChan.
|
||||
for {
|
||||
|
@ -164,5 +170,5 @@ func main() {
|
|||
// TIP: If you make use of it inside a web frontend application
|
||||
// then checkout the "optional.sse.js.html" to use the javascript's API for SSE,
|
||||
// it will also remove the browser's "loading" indicator while receiving those event messages.
|
||||
app.Run(iris.Addr(":8080"))
|
||||
app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// +build go1.11beta2
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
|
@ -340,6 +340,32 @@ type Context interface {
|
|||
// IsStopped checks and returns true if the current position of the Context is 255,
|
||||
// means that the StopExecution() was called.
|
||||
IsStopped() bool
|
||||
// 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 `http#CloseNotify`.
|
||||
// CloseNotify may wait to notify until Request.Body has been
|
||||
// fully read.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Look the `ResponseWriter#CloseNotifier` for more.
|
||||
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.
|
||||
//
|
||||
// Look the `Context#OnConnectionClose` and `ResponseWriter#SetBeforeFlush` for more.
|
||||
OnClose(cb func())
|
||||
|
||||
// +------------------------------------------------------------+
|
||||
// | Current "user/request" storage |
|
||||
|
@ -1355,6 +1381,76 @@ func (ctx *context) IsStopped() bool {
|
|||
return ctx.currentHandlerIndex == stopExecutionIndex
|
||||
}
|
||||
|
||||
// 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 `http#CloseNotify`.
|
||||
// CloseNotify may wait to notify until Request.Body has been
|
||||
// fully read.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Look the `ResponseWriter#CloseNotifier` for more.
|
||||
func (ctx *context) OnConnectionClose(cb func()) bool {
|
||||
// Note that `ctx.ResponseWriter().CloseNotify()` can already do the same
|
||||
// but it returns a channel which will never fire if it the protocol version is not compatible,
|
||||
// here we don't want to allocate an empty channel, just skip it.
|
||||
notifier, ok := ctx.writer.CloseNotifier()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
notify := notifier.CloseNotify()
|
||||
go func() {
|
||||
<-notify
|
||||
if cb != nil {
|
||||
cb()
|
||||
}
|
||||
}()
|
||||
|
||||
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.
|
||||
//
|
||||
// Look the `Context#OnConnectionClose` and `ResponseWriter#SetBeforeFlush` for more.
|
||||
func (ctx *context) OnClose(cb func()) {
|
||||
if cb == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Register the on underline connection close handler first.
|
||||
ctx.OnConnectionClose(cb)
|
||||
|
||||
// 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(cb)
|
||||
}
|
||||
|
||||
// +------------------------------------------------------------+
|
||||
// | Current "user/request" storage |
|
||||
// | and share information between the handlers - Values(). |
|
||||
|
|
|
@ -83,6 +83,21 @@ type ResponseWriter interface {
|
|||
|
||||
// WiteTo writes a response writer (temp: status code, headers and body) to another response writer
|
||||
WriteTo(ResponseWriter)
|
||||
|
||||
// Flusher indicates if `Flush` is supported by the client.
|
||||
//
|
||||
// The default HTTP/1.x and HTTP/2 ResponseWriter implementations
|
||||
// support Flusher, but ResponseWriter wrappers may not. Handlers
|
||||
// should always test for this ability at runtime.
|
||||
//
|
||||
// Note that even for ResponseWriters that support Flush,
|
||||
// if the client is connected through an HTTP proxy,
|
||||
// the buffered data may not reach the client until the response
|
||||
// completes.
|
||||
Flusher() (http.Flusher, bool)
|
||||
|
||||
// CloseNotifier indicates if the protocol supports the underline connection closure notification.
|
||||
CloseNotifier() (http.CloseNotifier, bool)
|
||||
}
|
||||
|
||||
// +------------------------------------------------------------+
|
||||
|
@ -296,21 +311,25 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|||
return nil, nil, errors.New("hijack is not supported by this ResponseWriter")
|
||||
}
|
||||
|
||||
// Flusher indicates if `Flush` is supported by the client.
|
||||
//
|
||||
// The default HTTP/1.x and HTTP/2 ResponseWriter implementations
|
||||
// support Flusher, but ResponseWriter wrappers may not. Handlers
|
||||
// should always test for this ability at runtime.
|
||||
//
|
||||
// Note that even for ResponseWriters that support Flush,
|
||||
// if the client is connected through an HTTP proxy,
|
||||
// the buffered data may not reach the client until the response
|
||||
// completes.
|
||||
func (w *responseWriter) Flusher() (http.Flusher, bool) {
|
||||
flusher, canFlush := w.ResponseWriter.(http.Flusher)
|
||||
return flusher, canFlush
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (w *responseWriter) Flush() {
|
||||
// The Flusher interface is implemented by ResponseWriters that allow
|
||||
// an HTTP handler to flush buffered data to the client.
|
||||
//
|
||||
// The default HTTP/1.x and HTTP/2 ResponseWriter implementations
|
||||
// support Flusher, but ResponseWriter wrappers may not. Handlers
|
||||
// should always test for this ability at runtime.
|
||||
//
|
||||
// Note that even for ResponseWriters that support Flush,
|
||||
// if the client is connected through an HTTP proxy,
|
||||
// the buffered data may not reach the client until the response
|
||||
// completes.
|
||||
if fl, isFlusher := w.ResponseWriter.(http.Flusher); isFlusher {
|
||||
fl.Flush()
|
||||
if flusher, ok := w.Flusher(); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -349,6 +368,12 @@ func (w *responseWriter) Push(target string, opts *http.PushOptions) error {
|
|||
return ErrPushNotSupported
|
||||
}
|
||||
|
||||
// CloseNotifier indicates if the protocol supports the underline connection closure notification.
|
||||
func (w *responseWriter) CloseNotifier() (http.CloseNotifier, bool) {
|
||||
notifier, supportsCloseNotify := w.ResponseWriter.(http.CloseNotifier)
|
||||
return notifier, supportsCloseNotify
|
||||
}
|
||||
|
||||
// CloseNotify returns a channel that receives at most a
|
||||
// single value (true) when the client connection has gone
|
||||
// away.
|
||||
|
@ -368,9 +393,10 @@ func (w *responseWriter) Push(target string, opts *http.PushOptions) error {
|
|||
// is a problem, use HTTP/2 or only use CloseNotify on methods
|
||||
// such as POST.
|
||||
func (w *responseWriter) CloseNotify() <-chan bool {
|
||||
if notifier, supportsCloseNotify := w.ResponseWriter.(http.CloseNotifier); supportsCloseNotify {
|
||||
if notifier, ok := w.CloseNotifier(); ok {
|
||||
return notifier.CloseNotify()
|
||||
}
|
||||
|
||||
ch := make(chan bool, 1)
|
||||
return ch
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user