From 7a7a43a229216d1485df535c18c54e5e52336706 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 16 Jul 2020 08:03:55 +0300 Subject: [PATCH] add 'DirOptions.PushTargets' for http/2 push on index(es) - #1562 --- HISTORY.md | 3 +- _examples/README.md | 1 + .../embedding-files-into-app/main.go | 3 +- .../file-server/http2push/assets/css/main.css | 3 + .../file-server/http2push/assets/favicon.ico | Bin 0 -> 15086 bytes .../file-server/http2push/assets/index.html | 18 +++ .../file-server/http2push/assets/js/main.js | 5 + _examples/file-server/http2push/main.go | 35 ++++++ _examples/file-server/http2push/mycert.crt | 31 +++++ _examples/file-server/http2push/mykey.key | 52 +++++++++ _examples/response-writer/http2push/main.go | 23 ++-- context/compress.go | 39 ++++++- context/response_recorder.go | 36 +++++- context/response_writer.go | 108 +++++------------- core/router/fs.go | 15 +++ go.mod | 2 +- 16 files changed, 275 insertions(+), 99 deletions(-) create mode 100644 _examples/file-server/http2push/assets/css/main.css create mode 100644 _examples/file-server/http2push/assets/favicon.ico create mode 100644 _examples/file-server/http2push/assets/index.html create mode 100644 _examples/file-server/http2push/assets/js/main.js create mode 100644 _examples/file-server/http2push/main.go create mode 100644 _examples/file-server/http2push/mycert.crt create mode 100644 _examples/file-server/http2push/mykey.key diff --git a/HISTORY.md b/HISTORY.md index cb4f984c8a..d3d04fc582 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -429,8 +429,9 @@ Other Improvements: New Package-level Variables: -- `iris.DirListRich` to override the default look and feel if the `DirOptions.ShowList` was set to true, can be passed to `DirOptions.DirList` field. - `iris.DirListRichOptions` to pass on `iris.DirListRich` method. +- `iris.DirListRich` to override the default look and feel if the `DirOptions.ShowList` was set to true, can be passed to `DirOptions.DirList` field. +- `DirOptions.PushTargets` for http/2 push on index [*](https://github.com/kataras/iris/tree/master/_examples/file-server/http2push/main.go). - `iris.Compress` and `iris.CompressReader` middleware to compress responses and decode compressed request data respectfully. - `iris.B, KB, MB, GB, TB, PB, EB` for byte units. - `TLSNoRedirect` to disable automatic "http://" to "https://" redirections (see below) diff --git a/_examples/README.md b/_examples/README.md index 2459edd3a5..54b6127fb7 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -78,6 +78,7 @@ * [Profiling](pprof/main.go) * File Server * [File Server](file-server/file-server/main.go) + * [HTTP/2 Push Targets](file-server/http2push/main.go) * [Favicon](file-server/favicon/main.go) * [Basic](file-server/basic/main.go) * [Embedding Files Into App Executable File](file-server/embedding-files-into-app/main.go) diff --git a/_examples/file-server/embedding-files-into-app/main.go b/_examples/file-server/embedding-files-into-app/main.go index 1b239cf88c..bb4be341d0 100644 --- a/_examples/file-server/embedding-files-into-app/main.go +++ b/_examples/file-server/embedding-files-into-app/main.go @@ -7,8 +7,7 @@ import ( // Follow these steps first: // $ go get -u github.com/go-bindata/go-bindata/... // $ go-bindata ./assets/... -// $ go build -// $ ./embedding-files-into-app +// $ go run . // "physical" files are not used, you can delete the "assets" folder and run the example. // // See `file-server/embedding-gziped-files-into-app` example as well. diff --git a/_examples/file-server/http2push/assets/css/main.css b/_examples/file-server/http2push/assets/css/main.css new file mode 100644 index 0000000000..7db3df1d78 --- /dev/null +++ b/_examples/file-server/http2push/assets/css/main.css @@ -0,0 +1,3 @@ +body { + background-color: black; +} diff --git a/_examples/file-server/http2push/assets/favicon.ico b/_examples/file-server/http2push/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c370da518ec542579b7cc0d5d30f4778b4a96318 GIT binary patch literal 15086 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 + + + + + + + + File Server + + + + + + + + + \ No newline at end of file diff --git a/_examples/file-server/http2push/assets/js/main.js b/_examples/file-server/http2push/assets/js/main.js new file mode 100644 index 0000000000..f97190ca62 --- /dev/null +++ b/_examples/file-server/http2push/assets/js/main.js @@ -0,0 +1,5 @@ +console.log("example"); + +function onClick() { + window.alert("button clicked"); +} \ No newline at end of file diff --git a/_examples/file-server/http2push/main.go b/_examples/file-server/http2push/main.go new file mode 100644 index 0000000000..a14d26ef89 --- /dev/null +++ b/_examples/file-server/http2push/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "github.com/kataras/iris/v12" +) + +var opts = iris.DirOptions{ + IndexName: "/index.html", + // Optionally register files (map's absolute values) to be served + // when a specific path (map's key WITHOUT prefix) is requested + // is fired before client asks (HTTP/2 Push). + // E.g. "/" (which serves the `IndexName` if not empty). + // + // Note: Requires running server under TLS, + // that's why we use ListenAndServeTLS below. + PushTargets: map[string][]string{ + "/": { + "/public/favicon.ico", + "/public/js/main.js", + "/public/css/main.css", + }, + }, + Compress: true, + ShowList: true, +} + +func main() { + app := iris.New() + app.HandleDir("/public", "./assets", opts) + + // Open your browser's Network tools, + // navigate to https://127.0.0.1/public. + // you should see `Initiator` tab: "Push / public". + app.Run(iris.TLS(":443", "mycert.crt", "mykey.key")) +} diff --git a/_examples/file-server/http2push/mycert.crt b/_examples/file-server/http2push/mycert.crt new file mode 100644 index 0000000000..9db93b09df --- /dev/null +++ b/_examples/file-server/http2push/mycert.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIUfwMd9auWixp19UnXOmyxJ9Jkv7IwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDA2MjUwOTUxNDdaFw0yMTA2 +MjUwOTUxNDdaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDlVGyGAQ9uyfNbwZyrtYOSjLpxf5NpNToh2OzU7gy2 +OexBji5lmWBQ3oYDG+FjAkbHORPzOMNpeMwje+IjGZBw8x6E+8WoGdSzbrEZ6pUV +wKJGKEuDlx6g6HEmtv3ZwgGe20gvPjjW+oCO888dwK/mbIHrHTq4nO3o0gAdAJwu +amn9BlHU5O4RW7BQ4tLF+j/fBCACWRG1NHXA0AT8eg544GyCdyteAH11oCDsHS8/ +DAPsM6t+tZrMCIt9+9dzPdVoOmQNaMMrcz8eJohddRTK6zHe9ixZTt/soayOF7OS +QQeekbr3HPYhD450zRVplLMHx7wnph/+O+Po6bqDnUzdnkqAAwwymQapHMuHXZKN +rhdfKau3rVo1GeXLIRgeWLUoxFSm4TYshrgt+0AidLRH+dCY7MS9Ngga/sAK3vID +gSF75mFgOhY+q7nvY9Ecao6TnoNNRY29hUat4y0VwSyysUy887vHr6lMK5CrAT/l +Ch8fuu20HUCoiLwMJvA6+wpivZkuiIvWY7bVGYsEYrrW+bCNN9wCGYTZEyX++os9 +v/38wdOqGUT00ewXkjIUFCWbrnxxSr98kF3w3wPf9K4Y40MNxeR90nyX4zjXGF1/ +91msUh+iivsz9mcN9DK83fgTyOsoVLX5cm/L2UBwMacsfjBbN4djOc5IuYMar/VN +GQIDAQABo1MwUTAdBgNVHQ4EFgQUtkf+yAvqgZC8f22iJny9hFEDolMwHwYDVR0j +BBgwFoAUtkf+yAvqgZC8f22iJny9hFEDolMwDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQsFAAOCAgEAE2QasBVru618rxupyJgEHw6r4iv7sz1Afz3Q5qJ4oSA9 +xVsrVCjr3iHRFSw8Rf670E8Ffk/JjzS65mHw6zeZj/ANBKQWLjRlqzYXeetq5HzG +SIgaG7p1RFvvzz3+leFGzjinZ6sKbfB4OB72o2YN+fO8DsDxgGKll0W4KAazizSe +HY9Pgu437tWnwF16rFO3IL47n5HzYlRoGIPOpzFoNX5+fyn9GlnKEtONF2QBKTjY +rdjvqFRByDiC74d8z/Yx8IiDRn1mTcG90JLR9+c6M7fruha9Y/rJfw+4AhVh5ZDz +Bl9rGPjwEs5zwutYvVAJzs7AVcighYP1lHKoJ7DxBDQeyBsYlUNk2l6bmZgLgGUZ ++2OyWlqc/jD2GdDsIaZ4i7QqhTI/6aYZIf5zUkblKV1aMSaDulKxRv//OwW28Jax +9EEoV7VaFb3sOkB/tZGhusXeQVtdrhahT3KkZLNwmNXoXWKJ5LjeUlFWJyV6JbDe +y/PIWWCwWqyuFCSZS+Cg3RDgAzfSxkI8uVZ+IKKJS3UluDX45lxXtbRrvTQ+oDrA +6ga5c1Vz9C4kn1K5yW4d7QIvg6vPiy7gvl+//sz9oxUM3yswInDBY0HKLgT0Uq9b +YzLDh2RSaHsgHMPy2BKqR+q2N+lpg7inAWuJM1Huq6eHFqhiyQkzsfscBd1Dpm8= +-----END CERTIFICATE----- diff --git a/_examples/file-server/http2push/mykey.key b/_examples/file-server/http2push/mykey.key new file mode 100644 index 0000000000..39f7b3af50 --- /dev/null +++ b/_examples/file-server/http2push/mykey.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +W2TzuSMYNDu876twbTVQJED3mwOAQ3J7 +-----END PRIVATE KEY----- diff --git a/_examples/response-writer/http2push/main.go b/_examples/response-writer/http2push/main.go index d7fce749a0..23ea1d80dc 100644 --- a/_examples/response-writer/http2push/main.go +++ b/_examples/response-writer/http2push/main.go @@ -4,7 +4,11 @@ // to need for the page they’re requesting. package main -import "github.com/kataras/iris/v12" +import ( + "net/http" + + "github.com/kataras/iris/v12" +) func main() { app := iris.New() @@ -22,14 +26,17 @@ func pushHandler(ctx iris.Context) { // If the target is a path, it will inherit the scheme and host of the // parent request. target := "/main.js" - err := ctx.ResponseWriter().Push(target, nil) - if err != nil { - if err == iris.ErrPushNotSupported { - ctx.StopWithText(iris.StatusHTTPVersionNotSupported, "HTTP/2 push not supported.") - } else { - ctx.StopWithError(iris.StatusInternalServerError, err) + + if pusher, ok := ctx.ResponseWriter().(http.Pusher); ok { + err := pusher.Push(target, nil) + if err != nil { + if err == iris.ErrPushNotSupported { + ctx.StopWithText(iris.StatusHTTPVersionNotSupported, "HTTP/2 push not supported.") + } else { + ctx.StopWithError(iris.StatusInternalServerError, err) + } + return } - return } ctx.HTML(``, target) diff --git a/context/compress.go b/context/compress.go index ea8f324177..a1d9b27223 100644 --- a/context/compress.go +++ b/context/compress.go @@ -164,6 +164,10 @@ type CompressResponseWriter struct { CompressWriter ResponseWriter + http.Pusher + http.Hijacker + http.CloseNotifier + Disabled bool Encoding string Level int @@ -195,12 +199,15 @@ func AcquireCompressResponseWriter(w ResponseWriter, r *http.Request, level int) if level == -1 && encoding == BROTLI { level = 6 } - // Writer exists, encoding matching and it's valid because it has a non nil encWriter; - // just reset to reduce allocations. - if v.Encoding == encoding && v.Level == level && v.CompressWriter != nil { - v.CompressWriter.Reset(w) - return v, nil - } + + /* + // Writer exists, encoding matching and it's valid because it has a non nil encWriter; + // just reset to reduce allocations. + if v.Encoding == encoding && v.Level == level && v.CompressWriter != nil { + v.CompressWriter.Reset(w) + return v, nil + } + */ v.Encoding = encoding @@ -213,6 +220,26 @@ func AcquireCompressResponseWriter(w ResponseWriter, r *http.Request, level int) v.CompressWriter = encWriter AddCompressHeaders(w.Header(), encoding) + + pusher, ok := w.(http.Pusher) + if !ok { + pusher = nil // make sure interface value is nil. + } + + hijacker, ok := w.(http.Hijacker) + if !ok { + hijacker = nil + } + + closeNotifier, ok := w.(http.CloseNotifier) + if !ok { + closeNotifier = nil + } + + v.Pusher = pusher + v.Hijacker = hijacker + v.CloseNotifier = closeNotifier + return v, nil } diff --git a/context/response_recorder.go b/context/response_recorder.go index 0b406d9147..caf511a3d4 100644 --- a/context/response_recorder.go +++ b/context/response_recorder.go @@ -1,6 +1,7 @@ package context import ( + "errors" "net/http" "sync" ) @@ -31,6 +32,10 @@ func releaseResponseRecorder(w *ResponseRecorder) { // rec := context.Recorder() type ResponseRecorder struct { ResponseWriter + + http.Hijacker + http.CloseNotifier + // keep track of the body in order to be // resetable and useful inside custom transactions chunks []byte @@ -50,6 +55,20 @@ func (w *ResponseRecorder) Naive() http.ResponseWriter { // prepares itself, the response recorder, to record and send response to the client. func (w *ResponseRecorder) BeginRecord(underline ResponseWriter) { w.ResponseWriter = underline + + hijacker, ok := underline.(http.Hijacker) + if !ok { + hijacker = nil + } + + closeNotifier, ok := underline.(http.CloseNotifier) + if !ok { + closeNotifier = nil + } + + w.Hijacker = hijacker + w.CloseNotifier = closeNotifier + w.headers = underline.Header() w.ResetBody() } @@ -236,6 +255,10 @@ func (w *ResponseRecorder) Flush() { w.ResetBody() } +// ErrPushNotSupported is returned by the Push method to +// indicate that HTTP/2 Push support is not available. +var ErrPushNotSupported = errors.New("push feature is not supported by this ResponseWriter") + // Push initiates an HTTP/2 server push. This constructs a synthetic // request using the given target and options, serializes that request // into a PUSH_PROMISE frame, then dispatches that request using the @@ -256,12 +279,19 @@ func (w *ResponseRecorder) Flush() { // // Push returns ErrPushNotSupported if the client has disabled push or if push // is not supported on the underlying connection. -func (w *ResponseRecorder) Push(target string, opts *http.PushOptions) error { +func (w *ResponseRecorder) Push(target string, opts *http.PushOptions) (err error) { w.FlushResponse() - err := w.ResponseWriter.Push(target, opts) + + if pusher, ok := w.ResponseWriter.(http.Pusher); ok { + err = pusher.Push(target, opts) + if err != nil && err.Error() == http.ErrNotSupported.ErrorString { + return ErrPushNotSupported + } + } + // NOTE: we have to reset them even if the push failed. w.ResetBody() w.ResetHeaders() - return err + return ErrPushNotSupported } diff --git a/context/response_writer.go b/context/response_writer.go index 573ccd1029..0816ce2c28 100644 --- a/context/response_writer.go +++ b/context/response_writer.go @@ -20,12 +20,6 @@ import ( // has returned. type ResponseWriter interface { http.ResponseWriter - http.Flusher - http.Hijacker - // Note: - // The http.CloseNotifier interface is deprecated. New code should use Request.Context instead. - http.CloseNotifier - http.Pusher // Naive returns the simple, underline and original http.ResponseWriter // that backends this response writer. @@ -87,10 +81,8 @@ type ResponseWriter interface { // 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. - // Warning: The http.CloseNotifier interface is deprecated. New code should use Request.Context instead. - CloseNotifier() (http.CloseNotifier, bool) + // Flush sends any buffered data to the client. + Flush() // required by compress writer. } // ResponseWriterBodyReseter can be implemented by @@ -139,6 +131,11 @@ func releaseResponseWriter(w ResponseWriter) { // it writes directly to the underline http.ResponseWriter type responseWriter struct { http.ResponseWriter + http.Pusher + http.Hijacker // Note: + // The http.CloseNotifier interface is deprecated. New code should use Request.Context instead. + http.CloseNotifier + statusCode int // the saved status code which will be used from the cache service // statusCodeSent bool // reply header has been (logically) written | no needed any more as we have a variable to catch total len of written bytes written int // the total size of bytes were written @@ -172,6 +169,29 @@ func (w *responseWriter) BeginResponse(underline http.ResponseWriter) { w.written = NoWritten w.statusCode = defaultStatusCode w.ResponseWriter = underline + + pusher, ok := underline.(http.Pusher) + if !ok { + pusher = nil // make sure interface value is nil. + } + + hijacker, ok := underline.(http.Hijacker) + if !ok { + hijacker = nil + } + + // This interface is obselete by Go authors + // and we only capture it + // for compatible reasons. End-developers SHOULD replace + // the use of CloseNotifier with the: Request.Context().Done() channel. + closeNotifier, ok := underline.(http.CloseNotifier) + if !ok { + closeNotifier = nil + } + + w.Pusher = pusher + w.Hijacker = hijacker + w.CloseNotifier = closeNotifier } // EndResponse is the last function which is called right before the server sent the final response. @@ -365,71 +385,3 @@ func (w *responseWriter) Flush() { flusher.Flush() } } - -// ErrPushNotSupported is returned by the Push method to -// indicate that HTTP/2 Push support is not available. -var ErrPushNotSupported = errors.New("push feature is not supported by this ResponseWriter") - -// Push initiates an HTTP/2 server push. This constructs a synthetic -// request using the given target and options, serializes that request -// into a PUSH_PROMISE frame, then dispatches that request using the -// server's request handler. If opts is nil, default options are used. -// -// The target must either be an absolute path (like "/path") or an absolute -// URL that contains a valid host and the same scheme as the parent request. -// If the target is a path, it will inherit the scheme and host of the -// parent request. -// -// The HTTP/2 spec disallows recursive pushes and cross-authority pushes. -// Push may or may not detect these invalid pushes; however, invalid -// pushes will be detected and canceled by conforming clients. -// -// Handlers that wish to push URL X should call Push before sending any -// data that may trigger a request for URL X. This avoids a race where the -// client issues requests for X before receiving the PUSH_PROMISE for X. -// -// Push returns ErrPushNotSupported if the client has disabled push or if push -// is not supported on the underlying connection. -func (w *responseWriter) Push(target string, opts *http.PushOptions) error { - if pusher, isPusher := w.ResponseWriter.(http.Pusher); isPusher { - err := pusher.Push(target, opts) - if err != nil && err.Error() == http.ErrNotSupported.ErrorString { - return ErrPushNotSupported - } - return err - } - 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. -// -// CloseNotify may wait to notify until Request.Body has been -// fully read. -// -// After the Handler has returned, there is no guarantee -// that the channel receives a value. -// -// If the protocol is HTTP/1.1 and CloseNotify is called while -// processing an idempotent request (such a GET) while -// HTTP/1.1 pipelining is in use, the arrival of a subsequent -// pipelined request may cause a value to be sent on the -// returned channel. In practice HTTP/1.1 pipelining is not -// enabled in browsers and not seen often in the wild. If this -// is a problem, use HTTP/2 or only use CloseNotify on methods -// such as POST. -func (w *responseWriter) CloseNotify() <-chan bool { - if notifier, ok := w.CloseNotifier(); ok { - return notifier.CloseNotify() - } - - ch := make(chan bool, 1) - return ch -} diff --git a/core/router/fs.go b/core/router/fs.go index 2e12ac0e73..fd108149d9 100644 --- a/core/router/fs.go +++ b/core/router/fs.go @@ -44,6 +44,11 @@ type DirOptions struct { // that another handler, called index handler, is auto-registered by the framework // if end developer does not managed to handle it by hand. IndexName string + // PushTargets optionally absolute filenames (map's value) to be served without any + // additional client's requests (HTTP/2 Push) + // when a specific path (map's key) is requested and + // it's not a directory (it's an `IndexFile`). + PushTargets map[string][]string // When files should served under compression. Compress bool @@ -472,6 +477,16 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { // // ctx.ResponseWriter().Header().Set(context.ContentEncodingHeaderKey, context.GzipHeaderValue) // } + if indexFound && len(options.PushTargets) > 0 && !options.Attachments.Enable { + if indexAssets, ok := options.PushTargets[name]; ok { + if pusher, ok := ctx.ResponseWriter().(http.Pusher); ok { + for _, indexAsset := range indexAssets { + pusher.Push(indexAsset, nil) + } + } + } + } + // If limit is 0 then same as ServeContent. ctx.ServeContentWithRate(f, info.Name(), info.ModTime(), options.Attachments.Limit, options.Attachments.Burst) if serveCode := ctx.GetStatusCode(); context.StatusCodeNotSuccessful(serveCode) { diff --git a/go.mod b/go.mod index c3669d9c0e..bd9bb35787 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.14 require ( github.com/BurntSushi/toml v0.3.1 - github.com/CloudyKit/jet/v4 v4.0.0 + github.com/CloudyKit/jet/v4 v4.0.2 github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398 github.com/andybalholm/brotli v1.0.1-0.20200619015827-c3da72aa01ed github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible