diff --git a/HISTORY.md b/HISTORY.md index 0034b187f9..bc93b4116f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -431,18 +431,18 @@ 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.ErrGzipNotSupported` to export the `context.ErrGzipNotSupported` when trying to write gzip but client does not support. -- `iris.GzipReader` middleware to decode gzip requests on next read actions. +- `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) - `CookieAllowReclaim`, `CookieAllowSubdomains`, `CookieSameSite`, `CookieSecure` and `CookieEncoding` to bring previously sessions-only features to all cookies in the request. New Context Methods: +- `Context.Compress(bool) error` and `Context.CompressReader(bool) error` - `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.CompressReader(enable bool)` method and `iris.CompressReader` middleware to enable future request read body calls to decompress data, [example](_examples/compression/main.go). - `Context.RegisterDependency(v interface{})` and `Context.UnregisterDependency(typ reflect.Type)` to register/remove struct dependencies on serve-time through a middleware. - `Context.SetID(id interface{})` and `Context.GetID() interface{}` added to register a custom unique indetifier to the Context, if necessary. - `Context.GetDomain() string` returns the domain. @@ -469,16 +469,22 @@ New Context Methods: Breaking Changes: +- `ctx.Gzip(boolean)` replaced with `ctx.Compress(boolean) error`. +- `ctx.GzipReader(boolean) error` replaced with `ctx.CompressReader(boolean) error`. +- `iris.Gzip` replaced with `iris.Compress` (middleware). +- `iris.GzipReader` replaced with `iris.CompressReader` (middleware). +- `ctx.ClientSupportsGzip() bool` replaced with `ctx.ClientSupportsEncoding("gzip", "br" ...) bool`. +- `ctx.GzipResponseWriter()` is **removed**. - `Party.HandleDir` now returns a list of `[]*Route` (GET and HEAD) instead of GET only. - `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). +- Fixed handler's error response not be respected when response recorder 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). - `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`. - `sessions#Config.Encode` and `Decode` are removed in favor of (the existing) `Encoding` field. - `versioning.GetVersion` now returns an empty string if version wasn't found. - Change the MIME type of `Javascript .js` and `JSONP` as the HTML specification now recommends to `"text/javascript"` instead of the obselete `"application/javascript"`. This change was pushed to the `Go` language itself as well. See . -- Remove the last input argument of `enableGzipCompression` in `Context.ServeContent`, `ServeFile` methods. This was deprecated a few versions ago. A middleware (`app.Use(iris.Gzip)`) or a prior call to `Context.Gzip(true)` will enable gzip compression. Also these two methods and `Context.SendFile` one now support `Content-Range` and `Accept-Ranges` correctly out of the box (`net/http` had a bug, which is now fixed). +- Remove the last input argument of `enableGzipCompression` in `Context.ServeContent`, `ServeFile` methods. This was deprecated a few versions ago. A middleware (`app.Use(iris.Compress)`) or a prior call to `Context.Compress(true)` will enable compression. Also these two methods and `Context.SendFile` one now support `Content-Range` and `Accept-Ranges` correctly out of the box (`net/http` had a bug, which is now fixed). - `Context.ServeContent` no longer returns an error, see `ServeContentWithRate`, `ServeFileWithRate` and `SendFileWithRate` new methods too. - `route.Trace() string` changed to `route.Trace(w io.Writer)`, to achieve the same result just pass a `bytes.Buffer` - `var mvc.AutoBinding` removed as the default behavior now resolves such dependencies automatically (see [[FEATURE REQUEST] MVC serving gRPC-compatible controller](https://github.com/kataras/iris/issues/1449)). @@ -486,6 +492,8 @@ Breaking Changes: - `mvc#BeforeActivation.Dependencies().Add` should be replaced with `mvc#BeforeActivation.Dependencies().Register` instead - **REMOVE** the `kataras/iris/v12/typescript` package in favor of the new [iris-cli](https://github.com/kataras/iris-cli). Also, the alm typescript online editor was removed as it is deprecated by its author, please consider using the [designtsx](https://designtsx.com/) instead. +There is a breaking change on the type alias of `iris.Context` which now points to the `*context.Context` instead of the `context.Context` interface. The **interface has been removed** and the ability to **override** the Context **is not** available any more. When we added the ability from end-developers to override the Context years ago, we have never imagine that we will ever had such a featured Context with more than 4000 lines of code. As of Iris 2020, it is difficult and un-productive from an end-developer to override the Iris Context, and as far as we know, nobody uses this feature anymore because of that exact reason. Beside the overriding feature support end, if you still use the `context.Context` instead of `iris.Context`, it's the time to do it: please find-and-replace to `iris.Context` as wikis, book and all examples shows for the past 3 years. For the 99.9% of the users there is no a single breaking change, you already using `iris.Context` so you are in the "safe zone". + # Su, 16 February 2020 | v12.1.8 New Features: diff --git a/NOTICE b/NOTICE index f664b4edc7..753c1e6400 100644 --- a/NOTICE +++ b/NOTICE @@ -22,7 +22,10 @@ Revision ID: d1c07411df0bb21f6b21f5b5d9325fac6f29c911 a16f63faca bluemonday 0a75d7616912ab9 https://github.com/microcosm-cc/ beb9cc6f7283ec1 bluemonday - 917c61b135 + 917c61b135 + brotli c3da72aa01ed78f https://github.com/andybalholm/brotli + 164593b9624fd91 + d25082d2d2 closestmatch 1fbe626be92eb4c https://github.com/schollz/closestmatch 347d182cae9f8f0 0a046bf2f4 @@ -97,4 +100,4 @@ Revision ID: d1c07411df0bb21f6b21f5b5d9325fac6f29c911 f5c1929ae8 uuid cb32006e483f2a2 https://github.com/google/uuid 3230e24209cf185 - c65b477dbf + c65b477dbf diff --git a/_examples/README.md b/_examples/README.md index 8831c8db51..2459edd3a5 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -54,9 +54,6 @@ * [Reverse Routing](routing/reverse/main.go) * [Router Wrapper](routing/custom-wrapper/main.go) * [Custom Router](routing/custom-router/main.go) - * Custom Context - * [Method Overriding](routing/custom-context/method-overriding/main.go) - * [New Implementation](routing/custom-context/new-implementation/main.go) * Subdomains * [Single](routing/subdomains/single/main.go) * [Multi](routing/subdomains/multi/main.go) @@ -129,12 +126,10 @@ * [Bind Custom per type](request-body/read-custom-per-type/main.go) * [Bind Custom via Unmarshaler](request-body/read-custom-via-unmarshaler/main.go) * [Bind Many times](request-body/read-many/main.go) - * [Read/Bind Gzip compressed data](request-body/read-gzip/main.go) * Response Writer * [Content Negotiation](response-writer/content-negotiation) * [Text, Markdown, YAML, HTML, JSON, JSONP, Msgpack, XML and Binary](response-writer/write-rest/main.go) * [Protocol Buffers](response-writer/protobuf/main.go) - * [Write Gzip](response-writer/write-gzip/main.go) * [HTTP/2 Server Push](response-writer/http2push/main.go) * [Stream Writer](response-writer/stream-writer/main.go) * [Transactions](response-writer/transactions/main.go) @@ -143,6 +138,10 @@ * Cache * [Simple](response-writer/cache/simple/main.go) * [Client-Side (304)](response-writer/cache/client-side/main.go) +* Compression + * [Server-Side](compression/main.go) + * [Client-Side](compression/client/main.go) + * [Client-Side (using Iris)](compress/client-using-iris/main.go) * Localization and Internationalization * [i18n](i18n/main.go) * Authentication, Authorization & Bot Detection diff --git a/_examples/compression/client-using-iris/main.go b/_examples/compression/client-using-iris/main.go new file mode 100644 index 0000000000..59775aab52 --- /dev/null +++ b/_examples/compression/client-using-iris/main.go @@ -0,0 +1,112 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/kataras/iris/v12/context" +) + +const baseURL = "http://localhost:8080" + +// Available options: +// - "gzip", +// - "deflate", +// - "br" (for brotli), +// - "snappy" and +// - "s2" +const encoding = context.BROTLI + +var client = http.DefaultClient + +func main() { + fmt.Printf("Running client example on: %s\n", baseURL) + + getExample() + postExample() +} + +func getExample() { + endpoint := baseURL + "/" + req, err := http.NewRequest(http.MethodGet, endpoint, nil) + if err != nil { + panic(err) + } + // Required to receive server's compressed data. + req.Header.Set("Accept-Encoding", encoding) + + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + // decompress server's compressed reply. + cr, err := context.NewCompressReader(resp.Body, encoding) + if err != nil { + panic(err) + } + defer cr.Close() + + body, err := ioutil.ReadAll(cr) + if err != nil { + panic(err) + } + + fmt.Printf("Received from server: %s", string(body)) +} + +type payload struct { + Username string `json:"username"` +} + +func postExample() { + buf := new(bytes.Buffer) + + // Compress client's data. + cw, err := context.NewCompressWriter(buf, encoding, -1) + if err != nil { + panic(err) + } + + json.NewEncoder(cw).Encode(payload{Username: "Edward"}) + + // `Close` or `Flush` required before `NewRequest` call. + cw.Close() + + endpoint := baseURL + "/" + + req, err := http.NewRequest(http.MethodPost, endpoint, buf) + if err != nil { + panic(err) + } + req.Header.Set("Content-Type", "application/json") + + // Required to send gzip compressed data to the server. + req.Header.Set("Content-Encoding", encoding) + // Required to receive server's compressed data. + req.Header.Set("Accept-Encoding", encoding) + + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + // Decompress server's compressed reply. + cr, err := context.NewCompressReader(resp.Body, encoding) + if err != nil { + panic(err) + } + defer cr.Close() // Closes the request body too. + + body, err := ioutil.ReadAll(cr) + if err != nil { + panic(err) + } + + fmt.Printf("Server replied with: %s", string(body)) +} diff --git a/_examples/compression/client/main.go b/_examples/compression/client/main.go new file mode 100644 index 0000000000..6c39294a8e --- /dev/null +++ b/_examples/compression/client/main.go @@ -0,0 +1,102 @@ +package main + +import ( + "bytes" + "compress/gzip" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +) + +var client = http.DefaultClient + +const baseURL = "http://localhost:8080" + +func main() { + fmt.Printf("Running client example on: %s\n", baseURL) + + getExample() + postExample() +} + +func getExample() { + endpoint := baseURL + "/" + req, err := http.NewRequest(http.MethodGet, endpoint, nil) + if err != nil { + panic(err) + } + // Required to receive server's compressed data. + req.Header.Set("Accept-Encoding", "gzip") + + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + // decompress server's compressed reply. + r, err := gzip.NewReader(resp.Body) + if err != nil { + panic(err) + } + defer r.Close() + + body, err := ioutil.ReadAll(r) + if err != nil { + panic(err) + } + + fmt.Printf("Received from server: %s", string(body)) +} + +type payload struct { + Username string `json:"username"` +} + +func postExample() { + buf := new(bytes.Buffer) + + // Compress client's data. + w := gzip.NewWriter(buf) + + b, err := json.Marshal(payload{Username: "Edward"}) + if err != nil { + panic(err) + } + w.Write(b) + w.Close() + + endpoint := baseURL + "/" + + req, err := http.NewRequest(http.MethodPost, endpoint, buf) + if err != nil { + panic(err) + } + req.Header.Set("Content-Type", "application/json") + + // Required to send gzip compressed data to the server. + req.Header.Set("Content-Encoding", "gzip") + // Required to receive server's compressed data. + req.Header.Set("Accept-Encoding", "gzip") + + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + // Decompress server's compressed reply. + r, err := gzip.NewReader(resp.Body) + if err != nil { + panic(err) + } + defer r.Close() + + body, err := ioutil.ReadAll(r) + if err != nil { + panic(err) + } + + fmt.Printf("Server replied with: %s", string(body)) +} diff --git a/_examples/compression/main.go b/_examples/compression/main.go new file mode 100644 index 0000000000..6f73a7e0d3 --- /dev/null +++ b/_examples/compression/main.go @@ -0,0 +1,64 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := newApp() + app.Logger().SetLevel("debug") + app.Listen(":8080") +} + +func newApp() *iris.Application { + app := iris.New() + // HERE and you are ready to GO: + app.Use(iris.Compress, iris.CompressReader) + + app.Get("/", send) + app.Post("/", receive) + + return app +} + +type payload struct { + Username string `json:"username"` +} + +func send(ctx iris.Context) { + ctx.JSON(payload{ + Username: "Makis", + }) +} + +func receive(ctx iris.Context) { + var p payload + if err := ctx.ReadJSON(&p); err != nil { + ctx.Application().Logger().Debugf("ReadJSON: %v", err) + } + + ctx.WriteString(p.Username) +} + +/* Manually: +func enableCompression(ctx iris.Context) { + // Enable writing using compression (deflate, gzip, brotli, snappy, s2): + err := ctx.Compress(true) + if err != nil { + ctx.Application().Logger().Debugf("writer: %v", err) + // if you REQUIRE server to SEND compressed data then `return` here. + // return + } + + // Enable reading and binding request's compressed data: + err = ctx.CompressReader(true) + if err != nil && + // on GET we don't expect writing with gzip from client + ctx.Method() != iris.MethodGet { + ctx.Application().Logger().Debugf("reader: %v", err) + // if you REQUIRE server to RECEIVE only + // compressed data then `return` here. + // return + } + + ctx.Next() +} +*/ diff --git a/_examples/compression/main_test.go b/_examples/compression/main_test.go new file mode 100644 index 0000000000..e63f013446 --- /dev/null +++ b/_examples/compression/main_test.go @@ -0,0 +1,41 @@ +package main + +import ( + "encoding/json" + "reflect" + "strings" + "testing" + + "github.com/kataras/iris/v12/context" + "github.com/kataras/iris/v12/httptest" +) + +func TestCompression(t *testing.T) { + app := newApp() + e := httptest.New(t, app) + + var expectedReply = payload{Username: "Makis"} + body := e.GET("/").WithHeader(context.AcceptEncodingHeaderKey, context.GZIP).Expect(). + Status(httptest.StatusOK). + ContentEncoding(context.GZIP). + ContentType(context.ContentJSONHeaderValue).Body().Raw() + + // Note that .Expect() consumes the response body + // and stores it to unexported "contents" field + // therefore, we retrieve it as string and put it to a new buffer. + r := strings.NewReader(body) + cr, err := context.NewCompressReader(r, context.GZIP) + if err != nil { + t.Fatal(err) + } + defer cr.Close() + + var got payload + if err = json.NewDecoder(cr).Decode(&got); err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(expectedReply, got) { + t.Fatalf("expected %#+v but got %#+v", expectedReply, got) + } +} diff --git a/_examples/convert-handlers/negroni-like/main.go b/_examples/convert-handlers/negroni-like/main.go index ff67100b13..be09c9e38c 100644 --- a/_examples/convert-handlers/negroni-like/main.go +++ b/_examples/convert-handlers/negroni-like/main.go @@ -39,6 +39,3 @@ func negronilikeTestMiddleware(w http.ResponseWriter, r *http.Request, next http w.WriteHeader(iris.StatusBadRequest) w.Write([]byte("Bad request")) } - -// Look "routing/custom-context" if you want to convert a custom handler with a custom Context -// to a context.Handler. diff --git a/_examples/convert-handlers/nethttp/main.go b/_examples/convert-handlers/nethttp/main.go index d56ccd8f16..d78c14ee83 100644 --- a/_examples/convert-handlers/nethttp/main.go +++ b/_examples/convert-handlers/nethttp/main.go @@ -29,6 +29,3 @@ func main() { func nativeTestMiddleware(w http.ResponseWriter, r *http.Request) { println("Request path: " + r.URL.Path) } - -// Look "routing/custom-context" if you want to convert a custom handler with a custom Context -// to a context.Handler. diff --git a/_examples/file-server/basic/main.go b/_examples/file-server/basic/main.go index 001e89b2f0..0060e7ddf4 100644 --- a/_examples/file-server/basic/main.go +++ b/_examples/file-server/basic/main.go @@ -23,7 +23,7 @@ func newApp() *iris.Application { // if end developer does not managed to handle it by hand. IndexName: "/index.html", // When files should served under compression. - Gzip: false, + Compress: false, // List the files inside the current requested directory if `IndexName` not found. ShowList: false, // If `ShowList` is true then this function will be used instead of the default one to show the list of files of a current requested directory(dir). diff --git a/_examples/file-server/file-server/main.go b/_examples/file-server/file-server/main.go index 993cd7a589..76610ba25b 100644 --- a/_examples/file-server/file-server/main.go +++ b/_examples/file-server/file-server/main.go @@ -54,7 +54,7 @@ func main() { filesRouter := app.Party("/files") { filesRouter.HandleDir("/", uploadDir, iris.DirOptions{ - Gzip: false, + Compress: true, ShowList: true, // Optionally, force-send files to the client inside of showing to the browser. diff --git a/_examples/file-server/file-server/views/dirlist.html b/_examples/file-server/file-server/views/dirlist.html index 2fecf72a5e..2b676f0c10 100644 --- a/_examples/file-server/file-server/views/dirlist.html +++ b/_examples/file-server/file-server/views/dirlist.html @@ -83,7 +83,11 @@ {{ range $idx, $file := .Files }} {{ $idx }} + {{ if $file.Download }} + {{ $file.Name }} + {{ else }} {{ $file.Name }} + {{ end }} {{ if $file.Info.IsDir }} Dir {{ else }} diff --git a/_examples/request-body/read-gzip/main.go b/_examples/request-body/read-gzip/main.go deleted file mode 100644 index def777b84f..0000000000 --- a/_examples/request-body/read-gzip/main.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "github.com/kataras/iris/v12" -) - -func main() { - app := newApp() - app.Logger().SetLevel("debug") - - app.Listen(":8080") -} - -type payload struct { - Message string `json:"message"` -} - -func newApp() *iris.Application { - app := iris.New() - - // GzipReader is a middleware which enables gzip decompression, - // when client sends gzip compressed data. - // - // A shortcut of: - // func(ctx iris.Context) { - // ctx.GzipReader(true) - // ctx.Next() - // } - app.Use(iris.GzipReader) - - app.Post("/", func(ctx iris.Context) { - // Bind incoming gzip compressed JSON to "p". - var p payload - if err := ctx.ReadJSON(&p); err != nil { - ctx.StopWithError(iris.StatusBadRequest, err) - return - } - - // Send back the message as plain text. - ctx.WriteString(p.Message) - }) - - return app -} diff --git a/_examples/request-body/read-gzip/main_test.go b/_examples/request-body/read-gzip/main_test.go deleted file mode 100644 index f75206eae9..0000000000 --- a/_examples/request-body/read-gzip/main_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package main - -import ( - "bytes" - "compress/gzip" - "encoding/json" - "testing" - - "github.com/kataras/iris/v12/httptest" -) - -func TestGzipReader(t *testing.T) { - app := newApp() - - expected := payload{Message: "test"} - b, err := json.Marshal(expected) - if err != nil { - t.Fatal(err) - } - - buf := new(bytes.Buffer) - w := gzip.NewWriter(buf) - _, err = w.Write(b) - if err != nil { - t.Fatal(err) - } - err = w.Close() - if err != nil { - t.Fatal(err) - } - - e := httptest.New(t, app) - // send gzip compressed. - e.POST("/").WithHeader("Content-Encoding", "gzip").WithHeader("Content-Type", "application/json"). - WithBytes(buf.Bytes()).Expect().Status(httptest.StatusOK).Body().Equal(expected.Message) - // raw. - e.POST("/").WithJSON(expected).Expect().Status(httptest.StatusOK).Body().Equal(expected.Message) -} diff --git a/_examples/response-writer/write-gzip/main.go b/_examples/response-writer/write-gzip/main.go deleted file mode 100644 index 080e41642c..0000000000 --- a/_examples/response-writer/write-gzip/main.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import "github.com/kataras/iris/v12" - -func main() { - app := iris.New() - // app.Use(iris.Gzip) - // func(ctx iris.Context) { ctx.Gzip(true/false)} - // OR: - app.Get("/", func(ctx iris.Context) { - ctx.WriteGzip([]byte("Hello World!")) - ctx.Header("X-Custom", - "Headers can be set here after WriteGzip as well, because the data are kept before sent to the client when using the context's GzipResponseWriter and ResponseRecorder.") - }) - - app.Get("/2", func(ctx iris.Context) { - // same as the `WriteGzip`. - // However GzipResponseWriter gives you more options, like - // reset data, disable and more, look its methods. - ctx.GzipResponseWriter().WriteString("Hello World!") - }) - - app.Listen(":8080") -} diff --git a/_examples/routing/custom-context/method-overriding/main.go b/_examples/routing/custom-context/method-overriding/main.go deleted file mode 100644 index edde898881..0000000000 --- a/_examples/routing/custom-context/method-overriding/main.go +++ /dev/null @@ -1,90 +0,0 @@ -package main - -// In this package I'll show you how to override the existing Context's functions and methods. -// You can easly navigate to the custom-context example to see how you can add new functions -// to your own context (need a custom handler). -// -// This way is far easier to understand and it's faster when you want to override existing methods: -import ( - "reflect" - - "github.com/kataras/iris/v12" - "github.com/kataras/iris/v12/context" -) - -// Create your own custom Context, put any fields you wanna need. -type MyContext struct { - // Optional Part 1: embed (optional but required if you don't want to override all context's methods) - iris.Context -} - -var _ iris.Context = &MyContext{} // optionally: validate on compile-time if MyContext implements context.Context. - -// The only one important if you will override the Context -// with an embedded context.Context inside it. -// Required in order to run the handlers via this "*MyContext". -func (ctx *MyContext) Do(handlers context.Handlers) { - context.Do(ctx, handlers) -} - -// The second one important if you will override the Context -// with an embedded context.Context inside it. -// Required in order to run the chain of handlers via this "*MyContext". -func (ctx *MyContext) Next() { - context.Next(ctx) -} - -// Override any context's method you want... -// [...] - -func (ctx *MyContext) HTML(format string, args ...interface{}) (int, error) { - ctx.Application().Logger().Infof("Executing .HTML function from MyContext") - - ctx.ContentType("text/html") - return ctx.Writef(format, args...) -} - -func main() { - app := iris.New() - // app.Logger().SetLevel("debug") - - // The only one Required: - // here is how you define how your own context will - // be created and acquired from the iris' generic context pool. - app.ContextPool.Attach(func() iris.Context { - return &MyContext{ - // Optional Part 3: - Context: context.NewContext(app), - } - }) - - // Register a view engine on .html files inside the ./view/** directory. - app.RegisterView(iris.HTML("./view", ".html")) - - // register your route, as you normally do - app.Handle("GET", "/", recordWhichContextJustForProofOfConcept, func(ctx iris.Context) { - // use the context's overridden HTML method. - ctx.HTML("

Hello from my custom context's HTML!

") - }) - - // this will be executed by the MyContext.Context - // if MyContext is not directly define the View function by itself. - app.Handle("GET", "/hi/{firstname:alphabetical}", recordWhichContextJustForProofOfConcept, func(ctx iris.Context) { - firstname := ctx.Params().Get("firstname") - - ctx.ViewData("firstname", firstname) - ctx.Gzip(true) - - ctx.View("hi.html") - }) - - app.Listen(":8080") -} - -// should always print "($PATH) Handler is executing from 'MyContext'" -func recordWhichContextJustForProofOfConcept(ctx iris.Context) { - ctx.Application().Logger().Infof("(%s) Handler is executing from: '%s'", ctx.Path(), reflect.TypeOf(ctx).Elem().Name()) - ctx.Next() -} - -// Look "new-implementation" to see how you can create an entirely new Context with new functions. diff --git a/_examples/routing/custom-context/method-overriding/view/hi.html b/_examples/routing/custom-context/method-overriding/view/hi.html deleted file mode 100644 index 7999c305f8..0000000000 --- a/_examples/routing/custom-context/method-overriding/view/hi.html +++ /dev/null @@ -1 +0,0 @@ -

Hi {{.firstname}}

\ No newline at end of file diff --git a/_examples/routing/custom-context/new-implementation/main.go b/_examples/routing/custom-context/new-implementation/main.go deleted file mode 100644 index c518cc61c8..0000000000 --- a/_examples/routing/custom-context/new-implementation/main.go +++ /dev/null @@ -1,103 +0,0 @@ -package main - -import ( - "sync" - - "github.com/kataras/iris/v12" - "github.com/kataras/iris/v12/sessions" -) - -// Owner is our application structure, it contains the methods or fields we need, -// think it as the owner of our *Context. -type Owner struct { - // define here the fields that are global - // and shared to all clients. - sessionsManager *sessions.Sessions -} - -// this package-level variable "application" will be used inside context to communicate with our global Application. -var owner = &Owner{ - sessionsManager: sessions.New(sessions.Config{Cookie: "mysessioncookie"}), -} - -// Context is our custom context. -// Let's implement a context which will give us access -// to the client's Session with a trivial `ctx.Session()` call. -type Context struct { - iris.Context - session *sessions.Session -} - -// Session returns the current client's session. -func (ctx *Context) Session() *sessions.Session { - // this help us if we call `Session()` multiple times in the same handler - if ctx.session == nil { - // start a new session if not created before. - ctx.session = owner.sessionsManager.Start(ctx.Context) - } - - return ctx.session -} - -// Bold will send a bold text to the client. -func (ctx *Context) Bold(text string) { - ctx.HTML("" + text + "") -} - -var contextPool = sync.Pool{New: func() interface{} { - return &Context{} -}} - -func acquire(original iris.Context) *Context { - ctx := contextPool.Get().(*Context) - ctx.Context = original // set the context to the original one in order to have access to iris's implementation. - ctx.session = nil // reset the session - return ctx -} - -func release(ctx *Context) { - contextPool.Put(ctx) -} - -// Handler will convert our handler of func(*Context) to an iris Handler, -// in order to be compatible with the HTTP API. -func Handler(h func(*Context)) iris.Handler { - return func(original iris.Context) { - ctx := acquire(original) - h(ctx) - release(ctx) - } -} - -func newApp() *iris.Application { - app := iris.New() - - // Work as you did before, the only difference - // is that the original context.Handler should be wrapped with our custom - // `Handler` function. - app.Get("/", Handler(func(ctx *Context) { - ctx.Bold("Hello from our *Context") - })) - - app.Post("/set", Handler(func(ctx *Context) { - nameFieldValue := ctx.FormValue("name") - ctx.Session().Set("name", nameFieldValue) - ctx.Writef("set session = " + nameFieldValue) - })) - - app.Get("/get", Handler(func(ctx *Context) { - name := ctx.Session().GetString("name") - ctx.Writef(name) - })) - - return app -} - -func main() { - app := newApp() - - // GET: http://localhost:8080 - // POST: http://localhost:8080/set - // GET: http://localhost:8080/get - app.Listen(":8080") -} diff --git a/_examples/routing/custom-context/new-implementation/main_test.go b/_examples/routing/custom-context/new-implementation/main_test.go deleted file mode 100644 index 49c782d54b..0000000000 --- a/_examples/routing/custom-context/new-implementation/main_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "testing" - - "github.com/kataras/iris/v12/httptest" -) - -func TestCustomContextNewImpl(t *testing.T) { - app := newApp() - e := httptest.New(t, app, httptest.URL("http://localhost:8080")) - - e.GET("/").Expect(). - Status(httptest.StatusOK). - ContentType("text/html"). - Body().Equal("Hello from our *Context") - - expectedName := "iris" - e.POST("/set").WithFormField("name", expectedName).Expect(). - Status(httptest.StatusOK). - Body().Equal("set session = " + expectedName) - - e.GET("/get").Expect(). - Status(httptest.StatusOK). - Body().Equal(expectedName) -} diff --git a/_examples/routing/custom-router/main.go b/_examples/routing/custom-router/main.go index 5d9e8e4be0..3ec24fe258 100644 --- a/_examples/routing/custom-router/main.go +++ b/_examples/routing/custom-router/main.go @@ -14,7 +14,7 @@ import ( Build(provider router.RoutesProvider) error - RouteExists reports whether a particular route exists. RouteExists(ctx iris.Context, method, path string) bool - - FireErrorCode(ctx context.Context) should handle the given ctx.GetStatusCode(). + - FireErrorCode(ctx iris.Context) should handle the given ctx.GetStatusCode(). For a more detailed, complete and useful example you can take a look at the iris' router itself which is located at: diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index f33ece350c..8cc0ce733d 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -179,7 +179,7 @@ func main() { app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 min(1) else 504}", func(ctx iris.Context) { id := ctx.Params().GetUint64Default("id", 0) friendid := ctx.Params().GetUint64Default("friendid", 0) - ctx.Writef("Hello id: %d looking for friend id: ", id, friendid) + ctx.Writef("Hello id: %d looking for friend id: %d", id, friendid) }) // this will throw e 504 error code instead of 404 if all route's macros not passed. // :uint8 0 to 255. diff --git a/_examples/view/herotemplate/app.go b/_examples/view/herotemplate/app.go index 3765f65e0f..76f5781264 100644 --- a/_examples/view/herotemplate/app.go +++ b/_examples/view/herotemplate/app.go @@ -17,7 +17,7 @@ func main() { app := iris.New() app.Get("/users", func(ctx iris.Context) { - ctx.Gzip(true) + ctx.Compress(true) ctx.ContentType("text/html") userList := []string{ @@ -35,11 +35,9 @@ func main() { // template.UserList(userList, buffer) // ctx.Write(buffer.Bytes()) - // using an io.Writer for automatic buffer management (i.e. hero built-in buffer pool), - // iris context implements the io.Writer by its ResponseWriter - // which is an enhanced version of the standard http.ResponseWriter - // but still 100% compatible, GzipResponseWriter too: - // _, err := template.UserListToWriter(userList, ctx.GzipResponseWriter()) + // iris context implements the io.Writer: + // _, err := template.UserListToWriter(userList, ctx) + // OR: buffer := new(bytes.Buffer) template.UserList(userList, buffer) diff --git a/_examples/view/overview/main.go b/_examples/view/overview/main.go index bfe47c0073..8222175ed3 100644 --- a/_examples/view/overview/main.go +++ b/_examples/view/overview/main.go @@ -14,9 +14,9 @@ func main() { // - {{ current }} app.RegisterView(iris.HTML("./templates", ".html")) app.Get("/", func(ctx iris.Context) { - ctx.ViewData("Name", "iris") // the .Name inside the ./templates/hi.html - ctx.Gzip(true) // enable gzip for big files - ctx.View("hi.html") // render the template with the file name relative to the './templates' + ctx.Compress(true) // enable compression based on Accept-Encoding (e.g. "gzip"). + ctx.ViewData("Name", "iris") // the .Name inside the ./templates/hi.html. + ctx.View("hi.html") // render the template with the file name relative to the './templates'. }) // http://localhost:8080/ diff --git a/_examples/view/quicktemplate/controllers/execute_template.go b/_examples/view/quicktemplate/controllers/execute_template.go index 02eb90052a..153f6136b7 100644 --- a/_examples/view/quicktemplate/controllers/execute_template.go +++ b/_examples/view/quicktemplate/controllers/execute_template.go @@ -6,9 +6,9 @@ import ( "github.com/kataras/iris/v12" ) -// ExecuteTemplate renders a "tmpl" partial template to the `context#ResponseWriter`. +// ExecuteTemplate renders a "tmpl" partial template to the `Context.ResponseWriter`. func ExecuteTemplate(ctx iris.Context, tmpl templates.Partial) { - ctx.Gzip(true) + ctx.Compress(true) ctx.ContentType("text/html") - templates.WriteTemplate(ctx.ResponseWriter(), tmpl) + templates.WriteTemplate(ctx, tmpl) } diff --git a/_examples/view/template_html_1/main.go b/_examples/view/template_html_1/main.go index 95ffc6fa33..9e8508ee44 100644 --- a/_examples/view/template_html_1/main.go +++ b/_examples/view/template_html_1/main.go @@ -16,7 +16,7 @@ func main() { // TIP: append .Reload(true) to reload the templates on each request. app.Get("/", func(ctx iris.Context) { - ctx.Gzip(true) + ctx.Compress(true) ctx.ViewData("", mypage{"My Page title", "Hello world!"}) ctx.View("mypage.html") // Note that: you can pass "layout" : "otherLayout.html" to bypass the config's Layout property diff --git a/_examples/view/template_jet_1_embedded/bindata.go b/_examples/view/template_jet_1_embedded/bindata.go index d76ad2ba01..11989e7b0f 100644 --- a/_examples/view/template_jet_1_embedded/bindata.go +++ b/_examples/view/template_jet_1_embedded/bindata.go @@ -201,9 +201,9 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ - "views/includes/_partial.jet": viewsIncludes_partialJet, - "views/includes/blocks.jet": viewsIncludesBlocksJet, - "views/index.jet": viewsIndexJet, + "views/includes/_partial.jet": viewsIncludes_partialJet, + "views/includes/blocks.jet": viewsIncludesBlocksJet, + "views/index.jet": viewsIndexJet, "views/layouts/application.jet": viewsLayoutsApplicationJet, } @@ -246,15 +246,16 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } + var _bintree = &bintree{nil, map[string]*bintree{ - "views": &bintree{nil, map[string]*bintree{ - "includes": &bintree{nil, map[string]*bintree{ - "_partial.jet": &bintree{viewsIncludes_partialJet, map[string]*bintree{}}, - "blocks.jet": &bintree{viewsIncludesBlocksJet, map[string]*bintree{}}, + "views": {nil, map[string]*bintree{ + "includes": {nil, map[string]*bintree{ + "_partial.jet": {viewsIncludes_partialJet, map[string]*bintree{}}, + "blocks.jet": {viewsIncludesBlocksJet, map[string]*bintree{}}, }}, - "index.jet": &bintree{viewsIndexJet, map[string]*bintree{}}, - "layouts": &bintree{nil, map[string]*bintree{ - "application.jet": &bintree{viewsLayoutsApplicationJet, map[string]*bintree{}}, + "index.jet": {viewsIndexJet, map[string]*bintree{}}, + "layouts": {nil, map[string]*bintree{ + "application.jet": {viewsLayoutsApplicationJet, map[string]*bintree{}}, }}, }}, }} @@ -305,4 +306,3 @@ func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } - diff --git a/_examples/webassembly/main.go b/_examples/webassembly/main.go index 81468afd04..37803c84fe 100644 --- a/_examples/webassembly/main.go +++ b/_examples/webassembly/main.go @@ -17,7 +17,7 @@ func main() { app.HandleDir("/", "./client") app.Get("/", func(ctx iris.Context) { - // ctx.Gzip(true) + // ctx.Compress(true) ctx.ServeFile("./client/hello.html") }) diff --git a/aliases.go b/aliases.go index a1c6bc13d7..1813a426cb 100644 --- a/aliases.go +++ b/aliases.go @@ -1,10 +1,15 @@ package iris import ( + "net/http" + + "github.com/kataras/iris/v12/cache" "github.com/kataras/iris/v12/context" + "github.com/kataras/iris/v12/core/handlerconv" "github.com/kataras/iris/v12/core/host" "github.com/kataras/iris/v12/core/router" "github.com/kataras/iris/v12/hero" + "github.com/kataras/iris/v12/view" ) type ( @@ -15,7 +20,7 @@ type ( // // Developers send responses to the client's request through a Context. // Developers get request information from the client's request by a Context. - Context = context.Context + Context = *context.Context // UnmarshalerFunc a shortcut, an alias for the `context#UnmarshalerFunc` type // which implements the `context#Unmarshaler` interface for reading request's body // via custom decoders, most of them already implement the `context#UnmarshalerFunc` @@ -126,7 +131,7 @@ type ( // Any custom or builtin `CookieOption` is valid, // see `CookiePath`, `CookieCleanPath`, `CookieExpires` and `CookieHTTPOnly` for more. // - // An alias for the `context/Context#CookieOption`. + // An alias for the `context.CookieOption`. CookieOption = context.CookieOption // N is a struct which can be passed on the `Context.Negotiate` method. // It contains fields which should be filled based on the `Context.Negotiation()` @@ -134,6 +139,395 @@ type ( // which should be a string or []byte. // It completes the `context/context.ContentSelector` interface. // - // An alias for the `context/Context#N`. + // An alias for the `context.N`. N = context.N ) + +// Constants for input argument at `router.RouteRegisterRule`. +// See `Party#SetRegisterRule`. +const ( + // RouteOverride replaces an existing route with the new one, the default rule. + RouteOverride = router.RouteOverride + // RouteSkip keeps the original route and skips the new one. + RouteSkip = router.RouteSkip + // RouteError log when a route already exists, shown after the `Build` state, + // server never starts. + RouteError = router.RouteError + // RouteOverlap will overlap the new route to the previous one. + // If the route stopped and its response can be reset then the new route will be execute. + RouteOverlap = router.RouteOverlap +) + +// Contains the enum values of the `Context.GetReferrer()` method, +// shortcuts of the context subpackage. +const ( + ReferrerInvalid = context.ReferrerInvalid + ReferrerIndirect = context.ReferrerIndirect + ReferrerDirect = context.ReferrerDirect + ReferrerEmail = context.ReferrerEmail + ReferrerSearch = context.ReferrerSearch + ReferrerSocial = context.ReferrerSocial + + ReferrerNotGoogleSearch = context.ReferrerNotGoogleSearch + ReferrerGoogleOrganicSearch = context.ReferrerGoogleOrganicSearch + ReferrerGoogleAdwords = context.ReferrerGoogleAdwords +) + +// NoLayout to disable layout for a particular template file +// A shortcut for the `view#NoLayout`. +const NoLayout = view.NoLayout + +var ( + // HTML view engine. + // Shortcut of the kataras/iris/view.HTML. + HTML = view.HTML + // Django view engine. + // Shortcut of the kataras/iris/view.Django. + Django = view.Django + // Handlebars view engine. + // Shortcut of the kataras/iris/view.Handlebars. + Handlebars = view.Handlebars + // Pug view engine. + // Shortcut of the kataras/iris/view.Pug. + Pug = view.Pug + // Amber view engine. + // Shortcut of the kataras/iris/view.Amber. + Amber = view.Amber + // Jet view engine. + // Shortcut of the kataras/iris/view.Jet. + Jet = view.Jet +) + +var ( + // Compress is a middleware which enables writing + // using compression, if client supports. + Compress = func(ctx Context) { + ctx.Compress(true) + ctx.Next() + } + // CompressReader is a middleware which enables decompression, + // when client sends compressed data. + // + // Similar to: func(ctx iris.Context) { + // ctx.CompressReader(true) + // ctx.Next() + // } + CompressReader = func(ctx Context) { + ctx.CompressReader(true) + ctx.Next() + } +) + +var ( + // RegisterOnInterrupt registers a global function to call when CTRL+C/CMD+C pressed or a unix kill command received. + // + // A shortcut for the `host#RegisterOnInterrupt`. + RegisterOnInterrupt = host.RegisterOnInterrupt + + // LimitRequestBodySize is a middleware which sets a request body size limit + // for all next handlers in the chain. + // + // A shortcut for the `context#LimitRequestBodySize`. + LimitRequestBodySize = context.LimitRequestBodySize + // NewConditionalHandler returns a single Handler which can be registered + // as a middleware. + // Filter is just a type of Handler which returns a boolean. + // Handlers here should act like middleware, they should contain `ctx.Next` to proceed + // to the next handler of the chain. Those "handlers" are registered to the per-request context. + // + // + // It checks the "filter" and if passed then + // it, correctly, executes the "handlers". + // + // If passed, this function makes sure that the Context's information + // about its per-request handler chain based on the new "handlers" is always updated. + // + // If not passed, then simply the Next handler(if any) is executed and "handlers" are ignored. + // Example can be found at: _examples/routing/conditional-chain. + // + // A shortcut for the `context#NewConditionalHandler`. + NewConditionalHandler = context.NewConditionalHandler + // FileServer returns a Handler which serves files from a specific system, phyisical, directory + // or an embedded one. + // The first parameter is the directory, relative to the executable program. + // The second optional parameter is any optional settings that the caller can use. + // + // See `Party#HandleDir` too. + // Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server + // A shortcut for the `router.FileServer`. + FileServer = router.FileServer + // DirListRich can be passed to `DirOptions.DirList` field + // to override the default file listing appearance. + // Read more at: `core/router.DirListRich`. + DirListRich = router.DirListRich + // StripPrefix returns a handler that serves HTTP requests + // by removing the given prefix from the request URL's Path + // and invoking the handler h. StripPrefix handles a + // request for a path that doesn't begin with prefix by + // replying with an HTTP 404 not found error. + // + // Usage: + // fileserver := iris.FileServer("./static_files", DirOptions {...}) + // h := iris.StripPrefix("/static", fileserver) + // app.Get("/static/{file:path}", h) + // app.Head("/static/{file:path}", h) + StripPrefix = router.StripPrefix + // FromStd converts native http.Handler, http.HandlerFunc & func(w, r, next) to context.Handler. + // + // Supported form types: + // .FromStd(h http.Handler) + // .FromStd(func(w http.ResponseWriter, r *http.Request)) + // .FromStd(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)) + // + // A shortcut for the `handlerconv#FromStd`. + FromStd = handlerconv.FromStd + // Cache is a middleware providing server-side cache functionalities + // to the next handlers, can be used as: `app.Get("/", iris.Cache, aboutHandler)`. + // It should be used after Static methods. + // See `iris#Cache304` for an alternative, faster way. + // + // Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/#caching + Cache = cache.Handler + // NoCache is a middleware which overrides the Cache-Control, Pragma and Expires headers + // in order to disable the cache during the browser's back and forward feature. + // + // A good use of this middleware is on HTML routes; to refresh the page even on "back" and "forward" browser's arrow buttons. + // + // See `iris#StaticCache` for the opposite behavior. + // + // A shortcut of the `cache#NoCache` + NoCache = cache.NoCache + // StaticCache middleware for caching static files by sending the "Cache-Control" and "Expires" headers to the client. + // It accepts a single input parameter, the "cacheDur", a time.Duration that it's used to calculate the expiration. + // + // If "cacheDur" <=0 then it returns the `NoCache` middleware instaed to disable the caching between browser's "back" and "forward" actions. + // + // Usage: `app.Use(iris.StaticCache(24 * time.Hour))` or `app.Use(iris.StaticCache(-1))`. + // A middleware, which is a simple Handler can be called inside another handler as well, example: + // cacheMiddleware := iris.StaticCache(...) + // func(ctx iris.Context){ + // cacheMiddleware(ctx) + // [...] + // } + // + // A shortcut of the `cache#StaticCache` + StaticCache = cache.StaticCache + // Cache304 sends a `StatusNotModified` (304) whenever + // the "If-Modified-Since" request header (time) is before the + // time.Now() + expiresEvery (always compared to their UTC values). + // Use this, which is a shortcut of the, `chache#Cache304` instead of the "github.com/kataras/iris/v12/cache" or iris.Cache + // for better performance. + // Clients that are compatible with the http RCF (all browsers are and tools like postman) + // will handle the caching. + // The only disadvantage of using that instead of server-side caching + // is that this method will send a 304 status code instead of 200, + // So, if you use it side by side with other micro services + // you have to check for that status code as well for a valid response. + // + // Developers are free to extend this method's behavior + // by watching system directories changes manually and use of the `ctx.WriteWithExpiration` + // with a "modtime" based on the file modified date, + // similar to the `HandleDir`(which sends status OK(200) and browser disk caching instead of 304). + // + // A shortcut of the `cache#Cache304`. + Cache304 = cache.Cache304 + + // CookieAllowReclaim accepts the Context itself. + // If set it will add the cookie to (on `CookieSet`, `CookieSetKV`, `CookieUpsert`) + // or remove the cookie from (on `CookieRemove`) the Request object too. + // + // A shortcut for the `context#CookieAllowReclaim`. + CookieAllowReclaim = context.CookieAllowReclaim + // CookieAllowSubdomains set to the Cookie Options + // in order to allow subdomains to have access to the cookies. + // It sets the cookie's Domain field (if was empty) and + // it also sets the cookie's SameSite to lax mode too. + // + // A shortcut for the `context#CookieAllowSubdomains`. + CookieAllowSubdomains = context.CookieAllowSubdomains + // CookieSameSite sets a same-site rule for cookies to set. + // SameSite allows a server to define a cookie attribute making it impossible for + // the browser to send this cookie along with cross-site requests. The main + // goal is to mitigate the risk of cross-origin information leakage, and provide + // some protection against cross-site request forgery attacks. + // + // See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details. + // + // A shortcut for the `context#CookieSameSite`. + CookieSameSite = context.CookieHTTPOnly + // CookieSecure sets the cookie's Secure option if the current request's + // connection is using TLS. See `CookieHTTPOnly` too. + // + // A shortcut for the `context#CookieSecure`. + CookieSecure = context.CookieSecure + // CookieHTTPOnly is a `CookieOption`. + // Use it to set the cookie's HttpOnly field to false or true. + // HttpOnly field defaults to true for `RemoveCookie` and `SetCookieKV`. + // + // A shortcut for the `context#CookieHTTPOnly`. + CookieHTTPOnly = context.CookieHTTPOnly + // CookiePath is a `CookieOption`. + // Use it to change the cookie's Path field. + // + // A shortcut for the `context#CookiePath`. + CookiePath = context.CookiePath + // CookieCleanPath is a `CookieOption`. + // Use it to clear the cookie's Path field, exactly the same as `CookiePath("")`. + // + // A shortcut for the `context#CookieCleanPath`. + CookieCleanPath = context.CookieCleanPath + // CookieExpires is a `CookieOption`. + // Use it to change the cookie's Expires and MaxAge fields by passing the lifetime of the cookie. + // + // A shortcut for the `context#CookieExpires`. + CookieExpires = context.CookieExpires + // CookieEncoding accepts a value which implements `Encode` and `Decode` methods. + // It calls its `Encode` on `Context.SetCookie, UpsertCookie, and SetCookieKV` methods. + // And on `Context.GetCookie` method it calls its `Decode`. + // + // A shortcut for the `context#CookieEncoding`. + CookieEncoding = context.CookieEncoding + + // IsErrPath can be used at `context#ReadForm` and `context#ReadQuery`. + // It reports whether the incoming error is type of `formbinder.ErrPath`, + // which can be ignored when server allows unknown post values to be sent by the client. + // + // A shortcut for the `context#IsErrPath`. + IsErrPath = context.IsErrPath + // ErrEmptyForm is the type error which API users can make use of + // to check if a form was empty on `Context.ReadForm`. + // + // A shortcut for the `context#ErrEmptyForm`. + ErrEmptyForm = context.ErrEmptyForm + // NewProblem returns a new Problem. + // Head over to the `Problem` type godoc for more. + // + // A shortcut for the `context#NewProblem`. + NewProblem = context.NewProblem + // XMLMap wraps a map[string]interface{} to compatible xml marshaler, + // in order to be able to render maps as XML on the `Context.XML` method. + // + // Example: `Context.XML(XMLMap("Root", map[string]interface{}{...})`. + // + // A shortcut for the `context#XMLMap`. + XMLMap = context.XMLMap + // ErrStopExecution if returned from a hero middleware or a request-scope dependency + // stops the handler's execution, see _examples/dependency-injection/basic/middleware. + ErrStopExecution = hero.ErrStopExecution + // ErrHijackNotSupported is returned by the Hijack method to + // indicate that Hijack feature is not available. + // + // A shortcut for the `context#ErrHijackNotSupported`. + ErrHijackNotSupported = context.ErrHijackNotSupported + // ErrPushNotSupported is returned by the Push method to + // indicate that HTTP/2 Push support is not available. + // + // A shortcut for the `context#ErrPushNotSupported`. + ErrPushNotSupported = context.ErrPushNotSupported +) + +// HTTP Methods copied from `net/http`. +const ( + MethodGet = http.MethodGet + MethodPost = http.MethodPost + MethodPut = http.MethodPut + MethodDelete = http.MethodDelete + MethodConnect = http.MethodConnect + MethodHead = http.MethodHead + MethodPatch = http.MethodPatch + MethodOptions = http.MethodOptions + MethodTrace = http.MethodTrace + // MethodNone is an iris-specific "virtual" method + // to store the "offline" routes. + MethodNone = router.MethodNone +) + +// HTTP status codes as registered with IANA. +// See: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml. +// Raw Copy from the future(tip) net/http std package in order to recude the import path of "net/http" for the users. +const ( + StatusContinue = http.StatusContinue + StatusSwitchingProtocols = http.StatusSwitchingProtocols + StatusProcessing = http.StatusProcessing + StatusEarlyHints = http.StatusEarlyHints + StatusOK = http.StatusOK + StatusCreated = http.StatusCreated + StatusAccepted = http.StatusAccepted + StatusNonAuthoritativeInfo = http.StatusNonAuthoritativeInfo + StatusNoContent = http.StatusNoContent + StatusResetContent = http.StatusResetContent + StatusPartialContent = http.StatusPartialContent + StatusMultiStatus = http.StatusMultiStatus + StatusAlreadyReported = http.StatusAlreadyReported + StatusIMUsed = http.StatusIMUsed + + StatusMultipleChoices = http.StatusMultipleChoices + StatusMovedPermanently = http.StatusMovedPermanently + StatusFound = http.StatusFound + StatusSeeOther = http.StatusSeeOther + StatusNotModified = http.StatusNotModified + StatusUseProxy = http.StatusUseProxy + + StatusTemporaryRedirect = http.StatusTemporaryRedirect + StatusPermanentRedirect = http.StatusPermanentRedirect + + StatusBadRequest = http.StatusBadRequest + StatusUnauthorized = http.StatusUnauthorized + StatusPaymentRequired = http.StatusPaymentRequired + StatusForbidden = http.StatusForbidden + StatusNotFound = http.StatusNotFound + StatusMethodNotAllowed = http.StatusMethodNotAllowed + StatusNotAcceptable = http.StatusNotAcceptable + StatusProxyAuthRequired = http.StatusProxyAuthRequired + StatusRequestTimeout = http.StatusRequestTimeout + StatusConflict = http.StatusConflict + StatusGone = http.StatusGone + StatusLengthRequired = http.StatusLengthRequired + StatusPreconditionFailed = http.StatusPreconditionFailed + StatusRequestEntityTooLarge = http.StatusRequestEntityTooLarge + StatusPayloadTooRage = StatusRequestEntityTooLarge + StatusRequestURITooLong = http.StatusRequestURITooLong + StatusUnsupportedMediaType = http.StatusUnsupportedMediaType + StatusRequestedRangeNotSatisfiable = http.StatusRequestedRangeNotSatisfiable + StatusExpectationFailed = http.StatusExpectationFailed + StatusTeapot = http.StatusTeapot + StatusMisdirectedRequest = http.StatusMisdirectedRequest + StatusUnprocessableEntity = http.StatusUnprocessableEntity + StatusLocked = http.StatusLocked + StatusFailedDependency = http.StatusFailedDependency + StatusTooEarly = http.StatusTooEarly + StatusUpgradeRequired = http.StatusUpgradeRequired + StatusPreconditionRequired = http.StatusPreconditionRequired + StatusTooManyRequests = http.StatusTooManyRequests + StatusRequestHeaderFieldsTooLarge = http.StatusRequestHeaderFieldsTooLarge + StatusUnavailableForLegalReasons = http.StatusUnavailableForLegalReasons + // Unofficial Client Errors. + StatusPageExpired = context.StatusPageExpired + StatusBlockedByWindowsParentalControls = context.StatusBlockedByWindowsParentalControls + StatusInvalidToken = context.StatusInvalidToken + StatusTokenRequired = context.StatusTokenRequired + // + StatusInternalServerError = http.StatusInternalServerError + StatusNotImplemented = http.StatusNotImplemented + StatusBadGateway = http.StatusBadGateway + StatusServiceUnavailable = http.StatusServiceUnavailable + StatusGatewayTimeout = http.StatusGatewayTimeout + StatusHTTPVersionNotSupported = http.StatusHTTPVersionNotSupported + StatusVariantAlsoNegotiates = http.StatusVariantAlsoNegotiates + StatusInsufficientStorage = http.StatusInsufficientStorage + StatusLoopDetected = http.StatusLoopDetected + StatusNotExtended = http.StatusNotExtended + StatusNetworkAuthenticationRequired = http.StatusNetworkAuthenticationRequired + // Unofficial Server Errors. + StatusBandwidthLimitExceeded = context.StatusBandwidthLimitExceeded + StatusInvalidSSLCertificate = context.StatusInvalidSSLCertificate + StatusSiteOverloaded = context.StatusSiteOverloaded + StatusSiteFrozen = context.StatusSiteFrozen + StatusNetworkReadTimeout = context.StatusNetworkReadTimeout +) + +// StatusText returns a text for the HTTP status code. It returns the empty +// string if the code is unknown. +// +// Shortcut for core/router#StatusText. +var StatusText = context.StatusText diff --git a/cache/browser.go b/cache/browser.go index 5ed1230fd4..e456b91ef0 100644 --- a/cache/browser.go +++ b/cache/browser.go @@ -31,7 +31,7 @@ const ( // A good use of this middleware is on HTML routes; to refresh the page even on "back" and "forward" browser's arrow buttons. // // See `cache#StaticCache` for the opposite behavior. -var NoCache = func(ctx context.Context) { +var NoCache = func(ctx *context.Context) { ctx.Header(context.CacheControlHeaderKey, CacheControlHeaderValue) ctx.Header(PragmaHeaderKey, PragmaNoCacheHeaderValue) ctx.Header(ExpiresHeaderKey, ExpiresNeverHeaderValue) @@ -59,7 +59,7 @@ var StaticCache = func(cacheDur time.Duration) context.Handler { } cacheControlHeaderValue := "public, max-age=" + strconv.Itoa(int(cacheDur.Seconds())) - return func(ctx context.Context) { + return func(ctx *context.Context) { cacheUntil := time.Now().Add(cacheDur).Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat()) ctx.Header(ExpiresHeaderKey, cacheUntil) ctx.Header(context.CacheControlHeaderKey, cacheControlHeaderValue) @@ -98,7 +98,7 @@ const ifNoneMatchHeaderKey = "If-None-Match" // // Read more at: https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching and // https://en.wikipedia.org/wiki/HTTP_ETag -var ETag = func(ctx context.Context) { +var ETag = func(ctx *context.Context) { key := ctx.Request().URL.Path ctx.Header(context.ETagHeaderKey, key) if match := ctx.GetHeader(ifNoneMatchHeaderKey); match == key { @@ -126,7 +126,7 @@ var ETag = func(ctx context.Context) { // can be used on Party's that contains a static handler, // i.e `HandleDir`. var Cache304 = func(expiresEvery time.Duration) context.Handler { - return func(ctx context.Context) { + return func(ctx *context.Context) { now := time.Now() if modified, err := ctx.CheckIfModifiedSince(now.Add(-expiresEvery)); !modified && err == nil { ctx.WriteNotModified() diff --git a/cache/cache_test.go b/cache/cache_test.go index 9470342839..f9a75bd1db 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -97,12 +97,12 @@ func TestClientNoCache(t *testing.T) { app := iris.New() var n uint32 - app.Get("/", cache.Handler(cacheDuration), func(ctx context.Context) { + app.Get("/", cache.Handler(cacheDuration), func(ctx *context.Context) { atomic.AddUint32(&n, 1) ctx.Write([]byte(expectedBodyStr)) }) - app.Get("/nocache", cache.Handler(cacheDuration), func(ctx context.Context) { + app.Get("/nocache", cache.Handler(cacheDuration), func(ctx *context.Context) { client.NoCache(ctx) // <---- atomic.AddUint32(&n, 1) ctx.Write([]byte(expectedBodyStr)) @@ -120,7 +120,7 @@ func TestCache(t *testing.T) { app.Use(cache.Handler(cacheDuration)) - app.Get("/", func(ctx context.Context) { + app.Get("/", func(ctx *context.Context) { atomic.AddUint32(&n, 1) ctx.Write([]byte(expectedBodyStr)) }) @@ -130,7 +130,7 @@ func TestCache(t *testing.T) { expectedBodyStr2 = "This is the other" ) - app.Get("/other", func(ctx context.Context) { + app.Get("/other", func(ctx *context.Context) { atomic.AddUint32(&n2, 1) ctx.Write([]byte(expectedBodyStr2)) }) @@ -154,7 +154,7 @@ func TestCacheValidator(t *testing.T) { app := iris.New() var n uint32 - h := func(ctx context.Context) { + h := func(ctx *context.Context) { atomic.AddUint32(&n, 1) ctx.Write([]byte(expectedBodyStr)) } @@ -164,7 +164,7 @@ func TestCacheValidator(t *testing.T) { managedCache := cache.Cache(cacheDuration) managedCache.AddRule(rule.Validator([]rule.PreValidator{ - func(ctx context.Context) bool { + func(ctx *context.Context) bool { // should always invalid for cache, don't bother to go to try to get or set cache return ctx.Request().URL.Path != "/invalid" }, @@ -173,7 +173,7 @@ func TestCacheValidator(t *testing.T) { managedCache2 := cache.Cache(cacheDuration) managedCache2.AddRule(rule.Validator(nil, []rule.PostValidator{ - func(ctx context.Context) bool { + func(ctx *context.Context) bool { // it's passed the Claim and now Valid checks if the response contains a header of "DONT" return ctx.ResponseWriter().Header().Get("DONT") == "" }, @@ -183,7 +183,7 @@ func TestCacheValidator(t *testing.T) { app.Get("/valid", validCache.ServeHTTP, h) app.Get("/invalid", managedCache.ServeHTTP, h) - app.Get("/invalid2", managedCache2.ServeHTTP, func(ctx context.Context) { + app.Get("/invalid2", managedCache2.ServeHTTP, func(ctx *context.Context) { atomic.AddUint32(&n, 1) ctx.Header("DONT", "DO not cache that response even if it was claimed") ctx.Write([]byte(expectedBodyStr)) diff --git a/cache/client/client.go b/cache/client/client.go index cb572cfd3c..5d89c42955 100644 --- a/cache/client/client.go +++ b/cache/client/client.go @@ -101,7 +101,7 @@ const ( // if <=minimumAllowedCacheDuration then the server will try to parse from "cache-control" header // // client-side function -func (h *ClientHandler) ServeHTTP(ctx context.Context) { +func (h *ClientHandler) ServeHTTP(ctx *context.Context) { // check for deniers, if at least one of them return true // for this specific request, then skip the whole cache if !h.rule.Claim(ctx) { diff --git a/cache/client/handler.go b/cache/client/handler.go index 0e10b01409..ffeae084ca 100644 --- a/cache/client/handler.go +++ b/cache/client/handler.go @@ -63,17 +63,17 @@ func (h *Handler) AddRule(r rule.Rule) *Handler { return h } -var emptyHandler = func(ctx context.Context) { +var emptyHandler = func(ctx *context.Context) { ctx.StopWithText(500, "cache: empty body handler") } -func parseLifeChanger(ctx context.Context) entry.LifeChanger { +func parseLifeChanger(ctx *context.Context) entry.LifeChanger { return func() time.Duration { return time.Duration(ctx.MaxAge()) * time.Second } } -func (h *Handler) ServeHTTP(ctx context.Context) { +func (h *Handler) ServeHTTP(ctx *context.Context) { // check for pre-cache validators, if at least one of them return false // for this specific request, then skip the whole cache bodyHandler := ctx.NextHandler() diff --git a/cache/client/rule/chained.go b/cache/client/rule/chained.go index a2765eafe1..20a29960d0 100644 --- a/cache/client/rule/chained.go +++ b/cache/client/rule/chained.go @@ -39,7 +39,7 @@ func Chained(rule Rule, next ...Rule) Rule { } // Claim validator -func (c *chainedRule) Claim(ctx context.Context) bool { +func (c *chainedRule) Claim(ctx *context.Context) bool { if !c.Rule.Claim(ctx) { return false } @@ -47,7 +47,7 @@ func (c *chainedRule) Claim(ctx context.Context) bool { } // Valid validator -func (c *chainedRule) Valid(ctx context.Context) bool { +func (c *chainedRule) Valid(ctx *context.Context) bool { if !c.Rule.Valid(ctx) { return false } diff --git a/cache/client/rule/conditional.go b/cache/client/rule/conditional.go index 6e50adb846..a07f0cd3f9 100644 --- a/cache/client/rule/conditional.go +++ b/cache/client/rule/conditional.go @@ -33,11 +33,11 @@ func Conditional(claimPredicate func() bool, validPredicate func() bool) Rule { } // Claim validator -func (c *conditionalRule) Claim(ctx context.Context) bool { +func (c *conditionalRule) Claim(ctx *context.Context) bool { return c.claimPredicate() } // Valid validator -func (c *conditionalRule) Valid(ctx context.Context) bool { +func (c *conditionalRule) Valid(ctx *context.Context) bool { return c.validPredicate() } diff --git a/cache/client/rule/header.go b/cache/client/rule/header.go index ab77dc0e04..b118fec477 100644 --- a/cache/client/rule/header.go +++ b/cache/client/rule/header.go @@ -45,11 +45,11 @@ func HeaderValid(valid ruleset.HeaderPredicate) Rule { } // Claim validator -func (h *headerRule) Claim(ctx context.Context) bool { +func (h *headerRule) Claim(ctx *context.Context) bool { return h.claim(ctx.Request().Header.Get) } // Valid validator -func (h *headerRule) Valid(ctx context.Context) bool { +func (h *headerRule) Valid(ctx *context.Context) bool { return h.valid(ctx.ResponseWriter().Header().Get) } diff --git a/cache/client/rule/not_satisfied.go b/cache/client/rule/not_satisfied.go index 3727ab6c45..cfd85f9ae3 100644 --- a/cache/client/rule/not_satisfied.go +++ b/cache/client/rule/not_satisfied.go @@ -13,10 +13,10 @@ func NotSatisfied() Rule { return ¬SatisfiedRule{} } -func (n *notSatisfiedRule) Claim(context.Context) bool { +func (n *notSatisfiedRule) Claim(*context.Context) bool { return false } -func (n *notSatisfiedRule) Valid(context.Context) bool { +func (n *notSatisfiedRule) Valid(*context.Context) bool { return false } diff --git a/cache/client/rule/rule.go b/cache/client/rule/rule.go index 3758c4c4b1..92fe252ff4 100644 --- a/cache/client/rule/rule.go +++ b/cache/client/rule/rule.go @@ -1,11 +1,9 @@ package rule -import ( - "github.com/kataras/iris/v12/context" -) +import "github.com/kataras/iris/v12/context" // Rule a superset of validators type Rule interface { - Claim(ctx context.Context) bool - Valid(ctx context.Context) bool + Claim(ctx *context.Context) bool + Valid(ctx *context.Context) bool } diff --git a/cache/client/rule/satisfied.go b/cache/client/rule/satisfied.go index 8e34eec6bb..9fd40875f0 100644 --- a/cache/client/rule/satisfied.go +++ b/cache/client/rule/satisfied.go @@ -15,10 +15,10 @@ func Satisfied() Rule { return &satisfiedRule{} } -func (n *satisfiedRule) Claim(context.Context) bool { +func (n *satisfiedRule) Claim(*context.Context) bool { return true } -func (n *satisfiedRule) Valid(context.Context) bool { +func (n *satisfiedRule) Valid(*context.Context) bool { return true } diff --git a/cache/client/rule/validator.go b/cache/client/rule/validator.go index a4e84bafd7..1f8ca36c8c 100644 --- a/cache/client/rule/validator.go +++ b/cache/client/rule/validator.go @@ -1,8 +1,6 @@ package rule -import ( - "github.com/kataras/iris/v12/context" -) +import "github.com/kataras/iris/v12/context" // Validators are introduced to implement the RFC about cache (https://tools.ietf.org/html/rfc7234#section-1.1). @@ -18,7 +16,7 @@ import ( // One function, accepts the request and returns false if should be denied/ignore, otherwise true. // if at least one return false then the original handler will execute as it's // and the whole cache action(set & get) should be ignored, it will be never go to the step of post-cache validations. -type PreValidator func(context.Context) bool +type PreValidator func(*context.Context) bool // PostValidator type is is introduced to implement the second part of the RFC about cache. // @@ -32,7 +30,7 @@ type PreValidator func(context.Context) bool // the PreValidator checks only for request. // // If a function of type of PostValidator returns true then the (shared-always) cache is allowed to be stored. -type PostValidator func(context.Context) bool +type PostValidator func(*context.Context) bool // validatorRule is a rule witch receives PreValidators and PostValidators // it's a 'complete set of rules', you can call it as a Responsible Validator, @@ -68,7 +66,7 @@ func Validator(preValidators []PreValidator, postValidators []PostValidator) Rul // Claim returns true if incoming request can claim for a cached handler // the original handler should run as it is and exit -func (v *validatorRule) Claim(ctx context.Context) bool { +func (v *validatorRule) Claim(ctx *context.Context) bool { // check for pre-cache validators, if at least one of them return false // for this specific request, then skip the whole cache for _, shouldProcess := range v.preValidators { @@ -82,7 +80,7 @@ func (v *validatorRule) Claim(ctx context.Context) bool { // Valid returns true if incoming request and post-response from the original handler // is valid to be store to the cache, if not(false) then the consumer should just exit // otherwise(true) the consumer should store the cached response -func (v *validatorRule) Valid(ctx context.Context) bool { +func (v *validatorRule) Valid(ctx *context.Context) bool { // check if it's a valid response, if it's not then just return. for _, valid := range v.postValidators { if !valid(ctx) { diff --git a/cache/client/ruleset.go b/cache/client/ruleset.go index d9d8fccca4..52ec35a1c0 100644 --- a/cache/client/ruleset.go +++ b/cache/client/ruleset.go @@ -28,6 +28,6 @@ var DefaultRuleSet = rule.Chained( // NoCache disables the cache for a particular request, // can be used as a middleware or called manually from the handler. -func NoCache(ctx context.Context) { +func NoCache(ctx *context.Context) { ctx.Header(cfg.NoCacheHeader, "true") } diff --git a/cli.go b/cli.go new file mode 100644 index 0000000000..c9eef3fa2d --- /dev/null +++ b/cli.go @@ -0,0 +1,120 @@ +package iris + +// +------------------------------------------------------------+ +// | Bridge code between iris-cli and iris web application | +// | https://github.com/kataras/iris-cli | +// +------------------------------------------------------------+ + +import ( + "bytes" + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/kataras/iris/v12/context" + "github.com/kataras/iris/v12/core/router" + "gopkg.in/yaml.v3" +) + +// injectLiveReload tries to check if this application +// runs under https://github.com/kataras/iris-cli and if so +// then it checks if the livereload is enabled and then injects +// the watch listener (js script) on every HTML response. +// It has a slight performance cost but +// this (iris-cli with watch and livereload enabled) +// is meant to be used only in development mode. +// It does a full reload at the moment and if the port changed +// at runtime it will fire 404 instead of redirecting to the correct port (that's a TODO). +// +// tryInjectLiveReload runs right before Build -> BuildRouter. +func injectLiveReload(contextPool *context.Pool, router *router.Router) (bool, error) { + conf := struct { + Running bool `yaml:"Running,omitempty"` + LiveReload struct { + Disable bool `yaml:"Disable"` + Port int `yaml:"Port"` + } `yaml:"LiveReload"` + }{} + // defaults to disabled here. + conf.LiveReload.Disable = true + + wd, err := os.Getwd() + if err != nil { + return false, err + } + + for _, path := range []string{".iris.yml" /*, "../.iris.yml", "../../.iris.yml" */} { + path = filepath.Join(wd, path) + + if _, err := os.Stat(path); err == nil { + inFile, err := os.OpenFile(path, os.O_RDONLY, 0644) + if err != nil { + return false, err + } + + dec := yaml.NewDecoder(inFile) + err = dec.Decode(&conf) + inFile.Close() + if err != nil { + return false, err + } + + break + } + } + + if !conf.Running || conf.LiveReload.Disable { + return false, nil + } + + scriptReloadJS := []byte(fmt.Sprintf(``, conf.LiveReload.Port)) + + bodyCloseTag := []byte("") + + wrapper := func(w http.ResponseWriter, r *http.Request, _ http.HandlerFunc) { + ctx := contextPool.Acquire(w, r) + rec := ctx.Recorder() // Record everything and write all in once at the Context release. + router.ServeHTTPC(ctx) // We directly call request handler with Context. + + if strings.HasPrefix(ctx.GetContentType(), "text/html") { + // delete(rec.Header(), context.ContentLengthHeaderKey) + + body := rec.Body() + + if idx := bytes.LastIndex(body, bodyCloseTag); idx > 0 { + // add the script right before last . + body = append(body[:idx], bytes.Replace(body[idx:], bodyCloseTag, append(scriptReloadJS, bodyCloseTag...), 1)...) + rec.SetBody(body) + } else { + // Just append it. + rec.Write(scriptReloadJS) // nolint:errcheck + } + + if _, has := rec.Header()[context.ContentLengthHeaderKey]; has { + rec.Header().Set(context.ContentLengthHeaderKey, fmt.Sprintf("%d", len(rec.Body()))) + } + } + + contextPool.Release(ctx) + } + + router.WrapRouter(wrapper) + return true, nil +} diff --git a/configuration.go b/configuration.go index ab5a66dd55..fcd77337cf 100644 --- a/configuration.go +++ b/configuration.go @@ -1,15 +1,10 @@ package iris import ( - "bytes" - "encoding/json" - "errors" "fmt" "io/ioutil" "net" - "net/http" "os" - "os/exec" "os/user" "path/filepath" "runtime" @@ -20,6 +15,7 @@ import ( "github.com/BurntSushi/toml" "github.com/kataras/sitemap" + "github.com/kataras/tunnel" "gopkg.in/yaml.v3" ) @@ -567,222 +563,14 @@ var WithTunneling = func(app *Application) { app.config.Tunneling = conf } -// Tunnel is the Tunnels field of the TunnelingConfiguration structure. -type Tunnel struct { - // Name is the only one required field, - // it is used to create and close tunnels, e.g. "MyApp". - // If this field is not empty then ngrok tunnels will be created - // when the iris app is up and running. - Name string `json:"name" yaml:"Name" toml:"Name"` - // Addr is basically optionally as it will be set through - // Iris built-in Runners, however, if `iris.Raw` is used - // then this field should be set of form 'hostname:port' - // because framework cannot be aware - // of the address you used to run the server on this custom runner. - Addr string `json:"addr,omitempty" yaml:"Addr" toml:"Addr"` -} - -// TunnelingConfiguration contains configuration -// for the optional tunneling through ngrok feature. -// Note that the ngrok should be already installed at the host machine. -type TunnelingConfiguration struct { - // AuthToken field is optionally and can be used - // to authenticate the ngrok access. - // ngrok authtoken - AuthToken string `json:"authToken,omitempty" yaml:"AuthToken" toml:"AuthToken"` - - // No... - // Config is optionally and can be used - // to load ngrok configuration from file system path. - // - // If you don't specify a location for a configuration file, - // ngrok tries to read one from the default location $HOME/.ngrok2/ngrok.yml. - // The configuration file is optional; no error is emitted if that path does not exist. - // Config string `json:"config,omitempty" yaml:"Config" toml:"Config"` - - // Bin is the system binary path of the ngrok executable file. - // If it's empty then the framework will try to find it through system env variables. - Bin string `json:"bin,omitempty" yaml:"Bin" toml:"Bin"` - - // WebUIAddr is the web interface address of an already-running ngrok instance. - // Iris will try to fetch the default web interface address(http://127.0.0.1:4040) - // to determinate if a ngrok instance is running before try to start it manually. - // However if a custom web interface address is used, - // this field must be set e.g. http://127.0.0.1:5050. - WebInterface string `json:"webInterface,omitempty" yaml:"WebInterface" toml:"WebInterface"` - - // Region is optionally, can be used to set the region which defaults to "us". - // Available values are: - // "us" for United States - // "eu" for Europe - // "ap" for Asia/Pacific - // "au" for Australia - // "sa" for South America - // "jp" forJapan - // "in" for India - Region string `json:"region,omitempty" yaml:"Region" toml:"Region"` - - // Tunnels the collection of the tunnels. - // One tunnel per Iris Host per Application, usually you only need one. - Tunnels []Tunnel `json:"tunnels" yaml:"Tunnels" toml:"Tunnels"` -} - -func (tc *TunnelingConfiguration) isEnabled() bool { - return tc != nil && len(tc.Tunnels) > 0 -} - -func (tc *TunnelingConfiguration) isNgrokRunning() bool { - resp, err := http.Get(tc.WebInterface) - if err != nil { - return false - } - - resp.Body.Close() - return true -} - -// https://ngrok.com/docs -type ngrokTunnel struct { - Name string `json:"name"` - Addr string `json:"addr"` - Proto string `json:"proto"` - Auth string `json:"auth"` - BindTLS bool `json:"bind_tls"` -} - -func (tc TunnelingConfiguration) startTunnel(t Tunnel, publicAddr *string) error { - tunnelAPIRequest := ngrokTunnel{ - Name: t.Name, - Addr: t.Addr, - Proto: "http", - BindTLS: true, - } - - if !tc.isNgrokRunning() { - ngrokBin := "ngrok" // environment binary. - - if tc.Bin == "" { - _, err := exec.LookPath(ngrokBin) - if err != nil { - ngrokEnvVar, found := os.LookupEnv("NGROK") - if !found { - return fmt.Errorf(`"ngrok" executable not found, please install it from: https://ngrok.com/download`) - } - - ngrokBin = ngrokEnvVar - } - } else { - ngrokBin = tc.Bin - } - - if tc.AuthToken != "" { - cmd := exec.Command(ngrokBin, "authtoken", tc.AuthToken) - err := cmd.Run() - if err != nil { - return err - } - } - - // start -none, start without tunnels. - // and finally the -log stdout logs to the stdout otherwise the pipe will never be able to read from, spent a lot of time on this lol. - cmd := exec.Command(ngrokBin, "start", "-none", "-log", "stdout") - - // if tc.Config != "" { - // cmd.Args = append(cmd.Args, []string{"-config", tc.Config}...) - // } - if tc.Region != "" { - cmd.Args = append(cmd.Args, []string{"-region", tc.Region}...) - } - - stdout, err := cmd.StdoutPipe() - if err != nil { - return err - } - - if err = cmd.Start(); err != nil { - return err - } - - p := make([]byte, 256) - okText := []byte("client session established") - for { - n, err := stdout.Read(p) - if err != nil { - return err - } - - // we need this one: - // msg="client session established" - // note that this will block if something terrible happens - // but ngrok's errors are strong so the error is easy to be resolved without any logs. - if bytes.Contains(p[:n], okText) { - break - } - } - } - - return tc.createTunnel(tunnelAPIRequest, publicAddr) -} - -func (tc TunnelingConfiguration) stopTunnel(t Tunnel) error { - url := fmt.Sprintf("%s/api/tunnels/%s", tc.WebInterface, t.Name) - req, err := http.NewRequest(http.MethodDelete, url, nil) - if err != nil { - return err - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != StatusNoContent { - return fmt.Errorf("stop return an unexpected status code: %d", resp.StatusCode) - } - - return nil -} - -func (tc TunnelingConfiguration) createTunnel(tunnelAPIRequest ngrokTunnel, publicAddr *string) error { - url := fmt.Sprintf("%s/api/tunnels", tc.WebInterface) - requestData, err := json.Marshal(tunnelAPIRequest) - if err != nil { - return err - } - - resp, err := http.Post(url, context.ContentJSONHeaderValue, bytes.NewBuffer(requestData)) - if err != nil { - return err - } - defer resp.Body.Close() - - type publicAddrOrErrResp struct { - PublicAddr string `json:"public_url"` - Details struct { - ErrorText string `json:"err"` // when can't bind more addresses, status code was successful. - } `json:"details"` - ErrMsg string `json:"msg"` // when ngrok is not yet ready, status code was unsuccessful. - } - - var apiResponse publicAddrOrErrResp - - err = json.NewDecoder(resp.Body).Decode(&apiResponse) - if err != nil { - return err - } - - if errText := apiResponse.ErrMsg; errText != "" { - return errors.New(errText) - } - - if errText := apiResponse.Details.ErrorText; errText != "" { - return errors.New(errText) - } - - *publicAddr = apiResponse.PublicAddr - return nil -} +type ( + // TunnelingConfiguration contains configuration + // for the optional tunneling through ngrok feature. + // Note that the ngrok should be already installed at the host machine. + TunnelingConfiguration = tunnel.Configuration + // Tunnel is the Tunnels field of the TunnelingConfiguration structure. + Tunnel = tunnel.Tunnel +) // Configuration holds the necessary settings for an Iris Application instance. // All fields are optionally, the default values will work for a common web application. @@ -904,7 +692,7 @@ type Configuration struct { // Defaults to false. DisableAutoFireStatusCode bool `json:"disableAutoFireStatusCode,omitempty" yaml:"DisableAutoFireStatusCode" toml:"DisableAutoFireStatusCode"` // ResetOnFireErrorCode if true then any previously response body or headers through - // response recorder or gzip writer will be ignored and the router + // response recorder will be ignored and the router // will fire the registered (or default) HTTP error handler instead. // See `core/router/handler#FireErrorCode` and `Context.EndRequest` for more details. // @@ -1210,7 +998,7 @@ func WithConfiguration(c Configuration) Configurator { main.SocketSharding = v } - if c.Tunneling.isEnabled() { + if len(c.Tunneling.Tunnels) > 0 { main.Tunneling = c.Tunneling } diff --git a/context/accept_header.go b/context/accept_header.go new file mode 100644 index 0000000000..56febdcd0e --- /dev/null +++ b/context/accept_header.go @@ -0,0 +1,206 @@ +package context + +import "strings" + +func negotiationMatch(in []string, priorities []string) string { + // e.g. + // match json: + // in: text/html, application/json + // prioritities: application/json + // not match: + // in: text/html, application/json + // prioritities: text/xml + // match html: + // in: text/html, application/json + // prioritities: */* + // not match: + // in: application/json + // prioritities: text/xml + // match json: + // in: text/html, application/* + // prioritities: application/json + + if len(priorities) == 0 { + return "" + } + + if len(in) == 0 { + return priorities[0] + } + + for _, accepted := range in { + for _, p := range priorities { + // wildcard is */* or text/* and etc. + // so loop through each char. + for i, n := 0, len(accepted); i < n; i++ { + if accepted[i] != p[i] { + break + } + + if accepted[i] == '*' || p[i] == '*' { + return p + } + + if i == n-1 { + return p + } + } + } + } + + return "" +} + +func negotiateAcceptHeader(in []string, offers []string, bestOffer string) string { + if bestOffer == "" { + bestOffer = IDENTITY + } + + bestQ := -1.0 + specs := parseAccept(in) + for _, offer := range offers { + for _, spec := range specs { + if spec.Q > bestQ && + (spec.Value == "*" || spec.Value == offer) { + bestQ = spec.Q + bestOffer = offer + } + } + } + if bestQ == 0 { + bestOffer = "" + } + return bestOffer +} + +// acceptSpec describes an Accept* header. +type acceptSpec struct { + Value string + Q float64 +} + +// parseAccept parses Accept* headers. +func parseAccept(in []string) (specs []acceptSpec) { +loop: + for _, s := range in { + for { + var spec acceptSpec + spec.Value, s = expectTokenSlash(s) + if spec.Value == "" { + continue loop + } + spec.Q = 1.0 + s = skipSpace(s) + if strings.HasPrefix(s, ";") { + s = skipSpace(s[1:]) + if !strings.HasPrefix(s, "q=") { + continue loop + } + spec.Q, s = expectQuality(s[2:]) + if spec.Q < 0.0 { + continue loop + } + } + specs = append(specs, spec) + s = skipSpace(s) + if !strings.HasPrefix(s, ",") { + continue loop + } + s = skipSpace(s[1:]) + } + } + return +} + +func skipSpace(s string) (rest string) { + i := 0 + for ; i < len(s); i++ { + if octetTypes[s[i]]&isSpace == 0 { + break + } + } + return s[i:] +} + +func expectTokenSlash(s string) (token, rest string) { + i := 0 + for ; i < len(s); i++ { + b := s[i] + if (octetTypes[b]&isToken == 0) && b != '/' { + break + } + } + return s[:i], s[i:] +} + +func expectQuality(s string) (q float64, rest string) { + switch { + case len(s) == 0: + return -1, "" + case s[0] == '0': + q = 0 + case s[0] == '1': + q = 1 + default: + return -1, "" + } + s = s[1:] + if !strings.HasPrefix(s, ".") { + return q, s + } + s = s[1:] + i := 0 + n := 0 + d := 1 + for ; i < len(s); i++ { + b := s[i] + if b < '0' || b > '9' { + break + } + n = n*10 + int(b) - '0' + d *= 10 + } + return q + float64(n)/float64(d), s[i:] +} + +// Octet types from RFC 2616. +var octetTypes [256]octetType + +type octetType byte + +const ( + isToken octetType = 1 << iota + isSpace +) + +func init() { + // OCTET = + // CHAR = + // CTL = + // CR = + // LF = + // SP = + // HT = + // <"> = + // CRLF = CR LF + // LWS = [CRLF] 1*( SP | HT ) + // TEXT = + // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> + // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT + // token = 1* + // qdtext = > + + for c := 0; c < 256; c++ { + var t octetType + isCtl := c <= 31 || c == 127 + isChar := 0 <= c && c <= 127 + isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0 + if strings.IndexRune(" \t\r\n", rune(c)) >= 0 { + t |= isSpace + } + if isChar && !isCtl && !isSeparator { + t |= isToken + } + octetTypes[c] = t + } +} diff --git a/context/application.go b/context/application.go index 6e3f848279..ab524859fe 100644 --- a/context/application.go +++ b/context/application.go @@ -35,7 +35,7 @@ type Application interface { // i.e: routing within a foreign context. // // It is ready to use after Build state. - ServeHTTPC(ctx Context) + ServeHTTPC(ctx *Context) // ServeHTTP is the main router handler which calls the .Serve and acquires a new context from the pool. // @@ -65,11 +65,11 @@ type Application interface { // then it will try to reset the headers and the body before calling the // registered (or default) error handler for that error code set by // `ctx.StatusCode` method. - FireErrorCode(ctx Context) + FireErrorCode(ctx *Context) // RouteExists reports whether a particular route exists // It will search from the current subdomain of context's host, if not inside the root domain. - RouteExists(ctx Context, method, path string) bool + RouteExists(ctx *Context, method, path string) bool // FindClosestPaths returns a list of "n" paths close to "path" under the given "subdomain". // // Order may change. diff --git a/context/compress.go b/context/compress.go new file mode 100644 index 0000000000..6a98ae0864 --- /dev/null +++ b/context/compress.go @@ -0,0 +1,273 @@ +package context + +import ( + "errors" + "fmt" + "io" + "net/http" + "sync" + + "github.com/andybalholm/brotli" + "github.com/klauspost/compress/flate" + "github.com/klauspost/compress/gzip" + "github.com/klauspost/compress/s2" + "github.com/klauspost/compress/snappy" +) + +// The available builtin compression algorithms. +const ( + GZIP = "gzip" + DEFLATE = "deflate" + BROTLI = "br" + SNAPPY = "snappy" + S2 = "s2" +) + +// IDENTITY no transformation whatsoever. +const IDENTITY = "identity" + +var ( + // ErrResponseNotCompressed returned from AcquireCompressResponseWriter + // when response's Content-Type header is missing due to golang/go/issues/31753 or + // when accept-encoding is empty. The caller should fallback to the original response writer. + ErrResponseNotCompressed = errors.New("compress: response will not be compressed") + // ErrRequestNotCompressed returned from NewCompressReader + // when request is not compressed. + ErrRequestNotCompressed = errors.New("compress: request is not compressed") + // ErrNotSupportedCompression returned from + // AcquireCompressResponseWriter, NewCompressWriter and NewCompressReader + // when the request's Accept-Encoding was not found in the server's supported + // compression algorithms. Check that error with `errors.Is`. + ErrNotSupportedCompression = errors.New("compress: unsupported compression") +) + +type ( + noOpWriter struct{} + + noOpReadCloser struct { + io.Reader + } +) + +var ( + _ io.ReadCloser = (*noOpReadCloser)(nil) + _ io.Writer = (*noOpWriter)(nil) +) + +func (w *noOpWriter) Write(p []byte) (int, error) { return 0, nil } + +func (r *noOpReadCloser) Close() error { + return nil +} + +// CompressWriter is an interface which all compress writers should implement. +type CompressWriter interface { + io.WriteCloser + // All known implementations contain `Flush` and `Reset` methods, + // so we wanna declare them upfront. + Flush() error + Reset(io.Writer) +} + +// NewCompressWriter returns a CompressWriter of "w" based on the given "encoding". +func NewCompressWriter(w io.Writer, encoding string, level int) (cw CompressWriter, err error) { + switch encoding { + case GZIP: + cw, err = gzip.NewWriterLevel(w, level) + case DEFLATE: // -1 default level, same for gzip. + cw, err = flate.NewWriter(w, level) + case BROTLI: // 6 default level. + if level == -1 { + level = 6 + } + cw = brotli.NewWriterLevel(w, level) + case SNAPPY: + cw = snappy.NewWriter(w) + case S2: + cw = s2.NewWriter(w) + default: + // Throw if "identity" is given. As this is not acceptable on "Content-Encoding" header. + // Only Accept-Encoding (client) can use that; it means, no transformation whatsoever. + err = ErrNotSupportedCompression + } + + return +} + +// CompressReader is a structure which wraps a compressed reader. +// It is used for determination across common request body and a compressed one. +type CompressReader struct { + io.ReadCloser + + // We need this to reset the body to its original state, if requested. + Src io.ReadCloser + // Encoding is the compression alogirthm is used to decompress and read the data. + Encoding string +} + +// NewCompressReader returns a new "compressReader" wrapper of "src". +// It returns `ErrRequestNotCompressed` if client's request data are not compressed +// or `ErrNotSupportedCompression` if server missing the decompression algorithm. +// Note: on server-side the request body (src) will be closed automaticaly. +func NewCompressReader(src io.Reader, encoding string) (*CompressReader, error) { + if encoding == "" || src == nil { + return nil, ErrRequestNotCompressed + } + + var ( + rc io.ReadCloser + err error + ) + + switch encoding { + case GZIP: + rc, err = gzip.NewReader(src) + case DEFLATE: + rc = &noOpReadCloser{flate.NewReader(src)} + case BROTLI: + rc = &noOpReadCloser{brotli.NewReader(src)} + case SNAPPY: + rc = &noOpReadCloser{snappy.NewReader(src)} + case S2: + rc = &noOpReadCloser{s2.NewReader(src)} + default: + err = ErrNotSupportedCompression + } + + if err != nil { + return nil, err + } + + srcReadCloser, ok := src.(io.ReadCloser) + if !ok { + srcReadCloser = &noOpReadCloser{src} + } + + return &CompressReader{ + ReadCloser: rc, + Src: srcReadCloser, + Encoding: encoding, + }, nil +} + +var compressWritersPool = sync.Pool{New: func() interface{} { return &CompressResponseWriter{} }} + +// AddCompressHeaders just adds the headers "Vary" to "Accept-Encoding" +// and "Content-Encoding" to the given encoding. +func AddCompressHeaders(h http.Header, encoding string) { + h.Set(VaryHeaderKey, AcceptEncodingHeaderKey) + h.Set(ContentEncodingHeaderKey, encoding) +} + +// CompressResponseWriter is a compressed data http.ResponseWriter. +type CompressResponseWriter struct { + CompressWriter + ResponseWriter + + Disabled bool + Encoding string + Level int +} + +var _ ResponseWriter = (*CompressResponseWriter)(nil) + +// AcquireCompressResponseWriter returns a CompressResponseWriter from the pool. +// It accepts an Iris response writer, a net/http request value and +// the level of compression (use -1 for default compression level). +// +// It returns the best candidate among "gzip", "defate", "br", "snappy" and "s2" +// based on the request's "Accept-Encoding" header value. +func AcquireCompressResponseWriter(w ResponseWriter, r *http.Request, level int) (*CompressResponseWriter, error) { + acceptEncoding := r.Header.Values(AcceptEncodingHeaderKey) + + if len(acceptEncoding) == 0 { + return nil, ErrResponseNotCompressed + } + + encoding := negotiateAcceptHeader(acceptEncoding, []string{"gzip", "deflate", "br", "snappy", "s2"}, "") + if encoding == "" { + return nil, fmt.Errorf("%w: %s", ErrNotSupportedCompression, encoding) + } + AddCompressHeaders(w.Header(), encoding) + + v := compressWritersPool.Get().(*CompressResponseWriter) + v.ResponseWriter = w + v.Disabled = false + 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 + } + + v.Encoding = encoding + + v.Level = level + encWriter, err := NewCompressWriter(w, encoding, level) + if err != nil { + return nil, err + } + + v.CompressWriter = encWriter + return v, nil +} + +func releaseCompressResponseWriter(w *CompressResponseWriter) { + compressWritersPool.Put(w) +} + +// FlushResponse flushes any data, closes the underline compress writer +// and writes the status code. +// Called automatically before `EndResponse`. +func (w *CompressResponseWriter) FlushResponse() { + if w.Disabled { + w.Header().Del(VaryHeaderKey) + w.Header().Del(ContentEncodingHeaderKey) + w.CompressWriter.Reset(&noOpWriter{}) + w.CompressWriter.Close() + } else { + w.ResponseWriter.Header().Del(ContentLengthHeaderKey) + w.CompressWriter.Close() // flushes and closes. + } + + w.ResponseWriter.FlushResponse() +} + +// EndResponse reeases the writers. +func (w *CompressResponseWriter) EndResponse() { + w.ResponseWriter.EndResponse() + releaseCompressResponseWriter(w) +} + +func (w *CompressResponseWriter) Write(p []byte) (int, error) { + if w.Disabled { + // If disabled or the content-type is empty the response will not be compressed (golang/go/issues/31753). + return w.ResponseWriter.Write(p) + } + + if w.Header().Get(ContentTypeHeaderKey) == "" { + w.Header().Set(ContentTypeHeaderKey, http.DetectContentType(p)) + } + + return w.CompressWriter.Write(p) +} + +// Flush sends any buffered data to the client. +// Can be called manually. +func (w *CompressResponseWriter) Flush() { + // if w.Disabled { + // w.Header().Del(VaryHeaderKey) + // w.Header().Del(ContentEncodingHeaderKey) + // } else { + // w.encWriter.Flush() + // } + + if !w.Disabled { + w.CompressWriter.Flush() + } + + w.ResponseWriter.Flush() +} diff --git a/context/context.go b/context/context.go index 3152cd7f64..2fe1fe8777 100644 --- a/context/context.go +++ b/context/context.go @@ -33,7 +33,6 @@ import ( "github.com/iris-contrib/blackfriday" "github.com/iris-contrib/schema" jsoniter "github.com/json-iterator/go" - "github.com/klauspost/compress/gzip" "github.com/microcosm-cc/bluemonday" "github.com/vmihailenco/msgpack/v5" "golang.org/x/net/publicsuffix" @@ -86,1177 +85,26 @@ func (u UnmarshalerFunc) Unmarshal(data []byte, v interface{}) error { return u(data, v) } -// Context is the midle-man server's "object" dealing with incoming requests. -// -// A New context is being acquired from a sync.Pool on each connection. -// The Context is the most important thing on the iris's http flow. -// -// Developers send responses to the client's request through a Context. -// Developers get request information from the client's request a Context. -// -// This context is an implementation of the context.Context sub-package. -// 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. - // - // Resets - // 1. handlers to nil. - // 2. values to empty. - // 3. the defer function. - // 4. response writer to the http.ResponseWriter. - // 5. request to the *http.Request. - BeginRequest(http.ResponseWriter, *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 OnClose function (if any). - // 2. flushes the response writer's result or fire any error handler. - // 3. releases the response writer. - EndRequest() - // 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 - // ResetResponseWriter should change or upgrade the Context's ResponseWriter. - ResetResponseWriter(ResponseWriter) - - // Request returns the original *http.Request, as expected. - Request() *http.Request - // ResetRequest sets the Context's Request, - // It is useful to store the new request created by a std *http.Request#WithContext() into Iris' Context. - // Use `ResetRequest` when for some reason you want to make a full - // override of the *http.Request. - // Note that: when you just want to change one of each fields you can use the Request() which returns a pointer to Request, - // so the changes will have affect without a full override. - // Usage: you use a native http handler which uses the standard "context" package - // to get values instead of the Iris' Context#Values(): - // r := ctx.Request() - // stdCtx := context.WithValue(r.Context(), key, val) - // ctx.ResetRequest(r.WithContext(stdCtx)). - ResetRequest(r *http.Request) - - // SetCurrentRoutes sets the route internally, - // See `GetCurrentRoute()` method too. - // It's being initialized by the Router. - // See `Exec` or `SetHandlers/AddHandler` methods to simulate a request. - SetCurrentRoute(route RouteReadOnly) - // GetCurrentRoute returns the current "read-only" route that - // was registered to this request's path. - GetCurrentRoute() RouteReadOnly - - // Do calls the SetHandlers(handlers) - // and executes the first handler, - // handlers should not be empty. - // - // It's used by the router, developers may use that - // to replace and execute handlers immediately. - Do(Handlers) - - // AddHandler can add handler(s) - // to the current request in serve-time, - // these handlers are not persistenced to the router. - // - // Router is calling this function to add the route's handler. - // If AddHandler called then the handlers will be inserted - // to the end of the already-defined route's handler. - AddHandler(...Handler) - // SetHandlers replaces all handlers with the new. - SetHandlers(Handlers) - // Handlers keeps tracking of the current handlers. - Handlers() Handlers - // HandlerIndex sets the current index of the - // current context's handlers chain. - // If n < 0 or the current handlers length is 0 then it just returns the - // current handler index without change the current index. - // - // Look Handlers(), Next() and StopExecution() too. - HandlerIndex(n int) (currentIndex int) - // Proceed is an alternative way to check if a particular handler - // has been executed and called the `ctx.Next` function inside it. - // This is useful only when you run a handler inside - // another handler. It justs checks for before index and the after index. - // - // A usecase example is when you want to execute a middleware - // inside controller's `BeginRequest` that calls the `ctx.Next` inside it. - // The Controller looks the whole flow (BeginRequest, method handler, EndRequest) - // as one handler, so `ctx.Next` will not be reflected to the method handler - // if called from the `BeginRequest`. - // - // Although `BeginRequest` should NOT be used to call other handlers, - // the `BeginRequest` has been introduced to be able to set - // common data to all method handlers before their execution. - // Controllers can accept middleware(s) from the MVC's Application's Router as normally. - // - // That said let's see an example of `ctx.Proceed`: - // - // var authMiddleware = basicauth.New(basicauth.Config{ - // Users: map[string]string{ - // "admin": "password", - // }, - // }) - // - // func (c *UsersController) BeginRequest(ctx iris.Context) { - // if !ctx.Proceed(authMiddleware) { - // ctx.StopExecution() - // } - // } - // This Get() will be executed in the same handler as `BeginRequest`, - // internally controller checks for `ctx.StopExecution`. - // So it will not be fired if BeginRequest called the `StopExecution`. - // func(c *UsersController) Get() []models.User { - // return c.Service.GetAll() - //} - // Alternative way is `!ctx.IsStopped()` if middleware make use of the `ctx.StopExecution()` on failure. - Proceed(Handler) bool - // HandlerName returns the current handler's name, helpful for debugging. - HandlerName() string - // HandlerFileLine returns the current running handler's function source file and line information. - // Useful mostly when debugging. - HandlerFileLine() (file string, line int) - // RouteName returns the route name that this handler is running on. - // Note that it will return empty on not found handlers. - RouteName() string - // Next calls all the next handler from the handlers chain, - // it should be used inside a middleware. - // - // Note: Custom context should override this method in order to be able to pass its own context.Context implementation. - Next() - // NextOr checks if chain has a next handler, if so then it executes it - // otherwise it sets a new chain assigned to this Context based on the given handler(s) - // and executes its first handler. - // - // Returns true if next handler exists and executed, otherwise false. - // - // Note that if no next handler found and handlers are missing then - // it sends a Status Not Found (404) to the client and it stops the execution. - NextOr(handlers ...Handler) bool - // NextOrNotFound checks if chain has a next handler, if so then it executes it - // otherwise it sends a Status Not Found (404) to the client and stops the execution. - // - // Returns true if next handler exists and executed, otherwise false. - NextOrNotFound() bool - // NextHandler returns (it doesn't execute) the next handler from the handlers chain. - // - // Use .Skip() to skip this handler if needed to execute the next of this returning handler. - NextHandler() Handler - // Skip skips/ignores the next handler from the handlers chain, - // it should be used inside a middleware. - Skip() - // StopExecution stops the handlers chain of this request. - // Meaning that any following `Next` calls are ignored, - // as a result the next handlers in the chain will not be fire. - StopExecution() - // IsStopped reports whether the current position of the context's handlers is -1, - // means that the StopExecution() was called at least once. - IsStopped() bool - // StopWithStatus stops the handlers chain and writes the "statusCode". - // - // If the status code is a failure one then - // it will also fire the specified error code handler. - StopWithStatus(statusCode int) - // StopWithText stops the handlers chain and writes the "statusCode" - // among with a message "plainText". - // - // If the status code is a failure one then - // it will also fire the specified error code handler. - StopWithText(statusCode int, plainText string) - // StopWithError stops the handlers chain and writes the "statusCode" - // among with the error "err". - // - // If the status code is a failure one then - // it will also fire the specified error code handler. - StopWithError(statusCode int, err error) - // StopWithJSON stops the handlers chain, writes the status code - // and sends a JSON response. - // - // If the status code is a failure one then - // it will also fire the specified error code handler. - StopWithJSON(statusCode int, jsonObject interface{}) - // StopWithProblem stops the handlers chain, writes the status code - // and sends an application/problem+json response. - // See `iris.NewProblem` to build a "problem" value correctly. - // - // If the status code is a failure one then - // it will also fire the specified error code handler. - StopWithProblem(statusCode int, problem Problem) - - // +------------------------------------------------------------+ - // | Current "user/request" storage | - // | and share information between the handlers - Values(). | - // | Save and get named path parameters - Params() | - // +------------------------------------------------------------+ - - // Params returns the current url's named parameters key-value storage. - // Named path parameters are being saved here. - // This storage, as the whole Context, is per-request lifetime. - Params() *RequestParams - - // Values returns the current "user" storage. - // Named path parameters and any optional data can be saved here. - // This storage, as the whole Context, is per-request lifetime. - // - // You can use this function to Set and Get local values - // that can be used to share information between handlers and middleware. - Values() *memstore.Store - - // +------------------------------------------------------------+ - // | Path, Host, Subdomain, IP, Headers, Localization etc... | - // +------------------------------------------------------------+ - - // Method returns the request.Method, the client's http method to the server. - Method() string - // Path returns the full request path, - // escaped if EnablePathEscape config field is true. - Path() string - // RequestPath returns the full request path, - // based on the 'escape'. - RequestPath(escape bool) string - // Host returns the host part of the current url. - // This method makes use of the `Configuration.HostProxyHeaders` field too. - Host() string - // Subdomain returns the subdomain of this request, if any. - // Note that this is a fast method which does not cover all cases. - Subdomain() (subdomain string) - // FindClosest returns a list of "n" paths close to - // this request based on subdomain and request path. - // - // Order may change. - // Example: https://github.com/kataras/iris/tree/master/_examples/routing/intelligence/manual - FindClosest(n int) []string - // IsWWW returns true if the current subdomain (if any) is www. - IsWWW() bool - // FullRqeuestURI returns the full URI, - // including the scheme, the host and the relative requested path/resource. - FullRequestURI() string - // RemoteAddr tries to parse and return the real client's request IP. - // - // Based on allowed headers names that can be modified from Configuration.RemoteAddrHeaders. - // - // If parse based on these headers fail then it will return the Request's `RemoteAddr` field - // which is filled by the server before the HTTP handler. - // - // Look `Configuration.RemoteAddrHeaders`, - // `Configuration.WithRemoteAddrHeader(...)`, - // `Configuration.WithoutRemoteAddrHeader(...)`and - // `Configuration.RemoteAddrPrivateSubnets` for more. - RemoteAddr() string - // GetHeader returns the request header's value based on its name. - GetHeader(name string) string - // GetDomain resolves and returns the server's domain. - GetDomain() string - // IsAjax returns true if this request is an 'ajax request'( XMLHttpRequest) - // - // There is no a 100% way of knowing that a request was made via Ajax. - // You should never trust data coming from the client, they can be easily overcome by spoofing. - // - // Note that "X-Requested-With" Header can be modified by any client(because of "X-"), - // so don't rely on IsAjax for really serious stuff, - // try to find another way of detecting the type(i.e, content type), - // there are many blogs that describe these problems and provide different kind of solutions, - // it's always depending on the application you're building, - // this is the reason why this `IsAjax`` is simple enough for general purpose use. - // - // Read more at: https://developer.mozilla.org/en-US/docs/AJAX - // and https://xhr.spec.whatwg.org/ - IsAjax() bool - // IsMobile checks if client is using a mobile device(phone or tablet) to communicate with this server. - // If the return value is true that means that the http client using a mobile - // device to communicate with the server, otherwise false. - // - // Keep note that this checks the "User-Agent" request header. - IsMobile() bool - // IsScript reports whether a client is a script. - IsScript() bool - // IsSSL reports whether the client is running under HTTPS SSL. - // - // See `IsHTTP2` too. - IsSSL() bool - // IsHTTP2 reports whether the protocol version for incoming request was HTTP/2. - // The client code always uses either HTTP/1.1 or HTTP/2. - // - // See `IsSSL` too. - IsHTTP2() bool - // IsGRPC reports whether the request came from a gRPC client. - IsGRPC() bool - // GetReferrer extracts and returns the information from the "Referer" (or "Referrer") header - // and url query parameter as specified in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy. - GetReferrer() Referrer - // SetLanguage force-sets the language for i18n, can be used inside a middleare. - // It has the highest priority over the rest and if it is empty then it is ignored, - // if it set to a static string of "default" or to the default language's code - // then the rest of the language extractors will not be called at all and - // the default language will be set instead. - // - // See `app.I18n.ExtractFunc` for a more organised way of the same feature. - SetLanguage(langCode string) - // GetLocale returns the current request's `Locale` found by i18n middleware. - // See `Tr` too. - GetLocale() Locale - // Tr returns a i18n localized message based on format with optional arguments. - // See `GetLocale` too. - // Example: https://github.com/kataras/iris/tree/master/_examples/i18n - Tr(format string, args ...interface{}) string - // +------------------------------------------------------------+ - // | Headers helpers | - // +------------------------------------------------------------+ - - // Header adds a header to the response writer. - Header(name string, value string) - - // ContentType sets the response writer's - // header "Content-Type" to the 'cType'. - ContentType(cType string) - // GetContentType returns the response writer's - // header value of "Content-Type". - GetContentType() string - // GetContentType returns the request's - // trim-ed(without the charset and priority values) - // header value of "Content-Type". - GetContentTypeRequested() string - // GetContentLength returns the request's - // header value of "Content-Length". - GetContentLength() int64 - - // StatusCode sets the status code header to the response. - // Look .`GetStatusCode` too. - StatusCode(statusCode int) - // GetStatusCode returns the current status code of the response. - // Look `StatusCode` too. - GetStatusCode() int - - // AbsoluteURI parses the "s" and returns its absolute URI form. - AbsoluteURI(s string) string - // Redirect sends a redirect response to the client - // to a specific url or relative path. - // accepts 2 parameters string and an optional int - // first parameter is the url to redirect - // second parameter is the http status should send, - // default is 302 (StatusFound), - // you can set it to 301 (Permant redirect) - // or 303 (StatusSeeOther) if POST method, - // or StatusTemporaryRedirect(307) if that's nessecery. - Redirect(urlToRedirect string, statusHeader ...int) - // +------------------------------------------------------------+ - // | Various Request and Post Data | - // +------------------------------------------------------------+ - - // URLParam returns true if the url parameter exists, otherwise false. - URLParamExists(name string) bool - // URLParamDefault returns the get parameter from a request, - // if not found then "def" is returned. - URLParamDefault(name string, def string) string - // URLParam returns the get parameter from a request, if any. - URLParam(name string) string - // URLParamTrim returns the url query parameter with trailing white spaces removed from a request. - URLParamTrim(name string) string - // URLParamEscape returns the escaped url query parameter from a request. - URLParamEscape(name string) string - // URLParamInt returns the url query parameter as int value from a request, - // returns -1 and an error if parse failed. - URLParamInt(name string) (int, error) - // URLParamIntDefault returns the url query parameter as int value from a request, - // if not found or parse failed then "def" is returned. - URLParamIntDefault(name string, def int) int - // URLParamInt32Default returns the url query parameter as int32 value from a request, - // if not found or parse failed then "def" is returned. - URLParamInt32Default(name string, def int32) int32 - // URLParamInt64 returns the url query parameter as int64 value from a request, - // returns -1 and an error if parse failed. - URLParamInt64(name string) (int64, error) - // URLParamInt64Default returns the url query parameter as int64 value from a request, - // if not found or parse failed then "def" is returned. - URLParamInt64Default(name string, def int64) int64 - // URLParamFloat64 returns the url query parameter as float64 value from a request, - // returns -1 and an error if parse failed. - URLParamFloat64(name string) (float64, error) - // URLParamFloat64Default returns the url query parameter as float64 value from a request, - // if not found or parse failed then "def" is returned. - URLParamFloat64Default(name string, def float64) float64 - // URLParamBool returns the url query parameter as boolean value from a request, - // returns an error if parse failed or not found. - URLParamBool(name string) (bool, error) - // URLParams returns a map of GET query parameters separated by comma if more than one - // it returns an empty map if nothing found. - URLParams() map[string]string - - // FormValueDefault returns a single parsed form value by its "name", - // including both the URL field's query parameters and the POST or PUT form data. - // - // Returns the "def" if not found. - FormValueDefault(name string, def string) string - // FormValue returns a single parsed form value by its "name", - // including both the URL field's query parameters and the POST or PUT form data. - FormValue(name string) string - // FormValues returns the parsed form data, including both the URL - // field's query parameters and the POST or PUT form data. - // - // The default form's memory maximum size is 32MB, it can be changed by the - // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument. - // - // NOTE: A check for nil is necessary. - FormValues() map[string][]string - - // PostValueDefault returns the parsed form data from POST, PATCH, - // or PUT body parameters based on a "name". - // - // If not found then "def" is returned instead. - PostValueDefault(name string, def string) string - // PostValue returns the parsed form data from POST, PATCH, - // or PUT body parameters based on a "name" - PostValue(name string) string - // PostValueTrim returns the parsed form data from POST, PATCH, - // or PUT body parameters based on a "name", without trailing spaces. - PostValueTrim(name string) string - // PostValueInt returns the parsed form data from POST, PATCH, - // or PUT body parameters based on a "name", as int. - // - // If not found returns -1 and a non-nil error. - PostValueInt(name string) (int, error) - // PostValueIntDefault returns the parsed form data from POST, PATCH, - // or PUT body parameters based on a "name", as int. - // - // If not found returns or parse errors the "def". - PostValueIntDefault(name string, def int) int - // PostValueInt64 returns the parsed form data from POST, PATCH, - // or PUT body parameters based on a "name", as float64. - // - // If not found returns -1 and a no-nil error. - PostValueInt64(name string) (int64, error) - // PostValueInt64Default returns the parsed form data from POST, PATCH, - // or PUT body parameters based on a "name", as int64. - // - // If not found or parse errors returns the "def". - PostValueInt64Default(name string, def int64) int64 - // PostValueFloat64 returns the parsed form data from POST, PATCH, - // or PUT body parameters based on a "name", as float64. - // - // If not found returns -1 and a non-nil error. - PostValueFloat64(name string) (float64, error) - // PostValueFloat64Default returns the parsed form data from POST, PATCH, - // or PUT body parameters based on a "name", as float64. - // - // If not found or parse errors returns the "def". - PostValueFloat64Default(name string, def float64) float64 - // PostValueBool returns the parsed form data from POST, PATCH, - // or PUT body parameters based on a "name", as bool. - // - // If not found or value is false, then it returns false, otherwise true. - PostValueBool(name string) (bool, error) - // PostValues returns all the parsed form data from POST, PATCH, - // or PUT body parameters based on a "name" as a string slice. - // - // The default form's memory maximum size is 32MB, it can be changed by the - // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument. - PostValues(name string) []string - // FormFile returns the first uploaded file that received from the client. - // - // The default form's memory maximum size is 32MB, it can be changed by the - // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument. - // - // Example: https://github.com/kataras/iris/tree/master/_examples/file-server/upload-file - FormFile(key string) (multipart.File, *multipart.FileHeader, error) - // UploadFormFiles uploads any received file(s) from the client - // to the system physical location "destDirectory". - // - // The second optional argument "before" gives caller the chance to - // modify the *miltipart.FileHeader before saving to the disk, - // it can be used to change a file's name based on the current request, - // all FileHeader's options can be changed. You can ignore it if - // you don't need to use this capability before saving a file to the disk. - // - // Note that it doesn't check if request body streamed. - // - // Returns the copied length as int64 and - // a not nil error if at least one new file - // can't be created due to the operating system's permissions or - // http.ErrMissingFile if no file received. - // - // If you want to receive & accept files and manage them manually you can use the `context#FormFile` - // instead and create a copy function that suits your needs, the below is for generic usage. - // - // The default form's memory maximum size is 32MB, it can be changed by the - // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument. - // - // See `FormFile` to a more controlled to receive a file. - // - // - // Example: https://github.com/kataras/iris/tree/master/_examples/file-server/upload-files - UploadFormFiles(destDirectory string, before ...func(Context, *multipart.FileHeader)) (n int64, err error) - - // +------------------------------------------------------------+ - // | Custom HTTP Errors | - // +------------------------------------------------------------+ - - // NotFound emits an error 404 to the client, using the specific custom error error handler. - // Note that you may need to call ctx.StopExecution() if you don't want the next handlers - // to be executed. Next handlers are being executed on iris because you can alt the - // error code and change it to a more specific one, i.e - // users := app.Party("/users") - // users.Done(func(ctx context.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }}) - NotFound() - - // +------------------------------------------------------------+ - // | Body Readers | - // +------------------------------------------------------------+ - - // SetMaxRequestBodySize sets a limit to the request body size - // should be called before reading the request body from the client. - SetMaxRequestBodySize(limitOverBytes int64) - - // GetBody reads and returns the request body. - // The default behavior for the http request reader is to consume the data readen - // but you can change that behavior by passing the `WithoutBodyConsumptionOnUnmarshal` iris option. - // - // However, whenever you can use the `ctx.Request().Body` instead. - GetBody() ([]byte, error) - // UnmarshalBody reads the request's body and binds it to a value or pointer of any type. - // Examples of usage: context.ReadJSON, context.ReadXML. - // - // Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-custom-via-unmarshaler/main.go - // - // UnmarshalBody does not check about gzipped data. - // Do not rely on compressed data incoming to your server. The main reason is: https://en.wikipedia.org/wiki/Zip_bomb - // However you are still free to read the `ctx.Request().Body io.Reader` manually. - UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) error - // ReadJSON reads JSON from request's body and binds it to a pointer of a value of any json-valid type. - // - // Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-json/main.go - ReadJSON(jsonObjectPtr interface{}) error - // ReadXML reads XML from request's body and binds it to a pointer of a value of any xml-valid type. - // - // Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-xml/main.go - ReadXML(xmlObjectPtr interface{}) error - // ReadYAML reads YAML from request's body and binds it to the "outPtr" value. - // - // Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-yaml/main.go - ReadYAML(outPtr interface{}) error - // ReadForm binds the request body of a form to the "formObject". - // It supports any kind of type, including custom structs. - // It will return nothing if request data are empty. - // The struct field tag is "form". - // Note that it will return nil error on empty form data if `Configuration.FireEmptyFormError` - // is false (as defaulted) in this case the caller should check the pointer to - // see if something was actually binded. - // - // If a client sent an unknown field, this method will return an error, - // in order to ignore that error use the `err != nil && !iris.IsErrPath(err)`. - // - // Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-form/main.go - ReadForm(formObject interface{}) error - // ReadQuery binds url query to "ptr". The struct field tag is "url". - // If a client sent an unknown field, this method will return an error, - // in order to ignore that error use the `err != nil && !iris.IsErrPath(err)`. - // - // Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-query/main.go - ReadQuery(ptr interface{}) error - // ReadProtobuf binds the body to the "ptr" of a proto Message and returns any error. - // Look `ReadJSONProtobuf` too. - ReadProtobuf(ptr proto.Message) error - // ReadJSONProtobuf reads a JSON body request into the given "ptr" proto.Message. - // Look `ReadProtobuf` too. - ReadJSONProtobuf(ptr proto.Message, opts ...protojson.UnmarshalOptions) error - // ReadMsgPack binds the request body of msgpack format to the "ptr" and returns any error. - ReadMsgPack(ptr interface{}) error - // ReadBody binds the request body to the "ptr" depending on the HTTP Method and the Request's Content-Type. - // If a GET method request then it reads from a form (or URL Query), otherwise - // it tries to match (depending on the request content-type) the data format e.g. - // JSON, Protobuf, MsgPack, XML, YAML, MultipartForm and binds the result to the "ptr". - ReadBody(ptr interface{}) error - - // +------------------------------------------------------------+ - // | Body (raw) Writers | - // +------------------------------------------------------------+ - - // Write writes the data to the connection as part of an HTTP reply. - // - // If WriteHeader has not yet been called, Write calls - // WriteHeader(http.StatusOK) before writing the data. If the Header - // does not contain a Content-Type line, Write adds a Content-Type set - // to the result of passing the initial 512 bytes of written data to - // DetectContentType. - // - // Depending on the HTTP protocol version and the client, calling - // Write or WriteHeader may prevent future reads on the - // Request.Body. For HTTP/1.x requests, handlers should read any - // needed request body data before writing the response. Once the - // headers have been flushed (due to either an explicit Flusher.Flush - // call or writing enough data to trigger a flush), the request body - // may be unavailable. For HTTP/2 requests, the Go HTTP server permits - // handlers to continue to read the request body while concurrently - // writing the response. However, such behavior may not be supported - // by all HTTP/2 clients. Handlers should read before writing if - // possible to maximize compatibility. - Write(body []byte) (int, error) - // Writef formats according to a format specifier and writes to the response. - // - // Returns the number of bytes written and any write error encountered. - Writef(format string, args ...interface{}) (int, error) - // WriteString writes a simple string to the response. - // - // Returns the number of bytes written and any write error encountered. - WriteString(body string) (int, error) - - // SetLastModified sets the "Last-Modified" based on the "modtime" input. - // If "modtime" is zero then it does nothing. - // - // It's mostly internally on core/router and context packages. - // - // Note that modtime.UTC() is being used instead of just modtime, so - // you don't have to know the internals in order to make that works. - SetLastModified(modtime time.Time) - // CheckIfModifiedSince checks if the response is modified since the "modtime". - // Note that it has nothing to do with server-side caching. - // It does those checks by checking if the "If-Modified-Since" request header - // sent by client or a previous server response header - // (e.g with WriteWithExpiration or HandleDir or Favicon etc.) - // is a valid one and it's before the "modtime". - // - // A check for !modtime && err == nil is necessary to make sure that - // it's not modified since, because it may return false but without even - // had the chance to check the client-side (request) header due to some errors, - // like the HTTP Method is not "GET" or "HEAD" or if the "modtime" is zero - // or if parsing time from the header failed. - // - // It's mostly used internally, e.g. `context#WriteWithExpiration`. See `ErrPreconditionFailed` too. - // - // Note that modtime.UTC() is being used instead of just modtime, so - // you don't have to know the internals in order to make that works. - CheckIfModifiedSince(modtime time.Time) (bool, error) - // WriteNotModified sends a 304 "Not Modified" status code to the client, - // it makes sure that the content type, the content length headers - // and any "ETag" are removed before the response sent. - // - // It's mostly used internally on core/router/fs.go and context methods. - WriteNotModified() - // WriteWithExpiration works like `Write` but it will check if a resource is modified, - // based on the "modtime" input argument, - // otherwise sends a 304 status code in order to let the client-side render the cached content. - WriteWithExpiration(body []byte, modtime time.Time) (int, error) - // StreamWriter registers the given stream writer for populating - // response body. - // - // Access to context's and/or its' members is forbidden from writer. - // - // This function may be used in the following cases: - // - // * if response body is too big (more than iris.LimitRequestBodySize(if set)). - // * if response body is streamed from slow external sources. - // * if response body must be streamed to the client in chunks. - // (aka `http server push`). - StreamWriter(writer func(w io.Writer) error) error - - // +------------------------------------------------------------+ - // | Body Writers with compression | - // +------------------------------------------------------------+ - // ClientSupportsGzip retruns true if the client supports gzip compression. - ClientSupportsGzip() bool - // WriteGzip accepts bytes, which are compressed to gzip format and sent to the client. - // returns the number of bytes written and an error ( if the client doesn' supports gzip compression) - // You may re-use this function in the same handler - // to write more data many times without any troubles. - WriteGzip(b []byte) (int, error) - // TryWriteGzip accepts bytes, which are compressed to gzip format and sent to the client. - // If client does not supprots gzip then the contents are written as they are, uncompressed. - TryWriteGzip(b []byte) (int, error) - // GzipResponseWriter converts the current response writer into a response writer - // which when its .Write called it compress the data to gzip and writes them to the client. - // - // Can be also disabled with its .Disable and .ResetBody to rollback to the usual response writer. - GzipResponseWriter() *GzipResponseWriter - // Gzip enables or disables (if enabled before) the gzip response writer,if the client - // supports gzip compression, so the following response data will - // be sent as compressed gzip data to the client. - Gzip(enable bool) - // GzipReader accepts a boolean, which, if set to true - // it wraps the request body reader with a gzip reader one (decompress request data on read). - // If the "enable" input argument is false then the request body will reset to the default one. - // - // Useful when incoming request data are gzip compressed. - // All future calls of `ctx.GetBody/ReadXXX/UnmarshalBody` methods will respect this option. - // - // Usage: - // app.Use(func(ctx iris.Context){ - // ctx.GzipReader(true) - // ctx.Next() - // }) - // - // If a client request's body is not gzip compressed then - // it returns with a `ErrGzipNotSupported` error, which can be safety ignored. - // - // See `GzipReader` package-level middleware too. - GzipReader(enable bool) error - - // +------------------------------------------------------------+ - // | Rich Body Content Writers/Renderers | - // +------------------------------------------------------------+ - - // ViewLayout sets the "layout" option if and when .View - // is being called afterwards, in the same request. - // Useful when need to set or/and change a layout based on the previous handlers in the chain. - // - // Note that the 'layoutTmplFile' argument can be set to iris.NoLayout || view.NoLayout - // to disable the layout for a specific view render action, - // it disables the engine's configuration's layout property. - // - // Look .ViewData and .View too. - // - // Example: https://github.com/kataras/iris/tree/master/_examples/view/context-view-data/ - ViewLayout(layoutTmplFile string) - // ViewData saves one or more key-value pair in order to be passed if and when .View - // is being called afterwards, in the same request. - // Useful when need to set or/and change template data from previous hanadlers in the chain. - // - // If .View's "binding" argument is not nil and it's not a type of map - // then these data are being ignored, binding has the priority, so the main route's handler can still decide. - // If binding is a map or context.Map then these data are being added to the view data - // and passed to the template. - // - // After .View, the data are not destroyed, in order to be re-used if needed (again, in the same request as everything else), - // to clear the view data, developers can call: - // ctx.Set(ctx.Application().ConfigurationReadOnly().GetViewDataContextKey(), nil) - // - // If 'key' is empty then the value is added as it's (struct or map) and developer is unable to add other value. - // - // Look .ViewLayout and .View too. - // - // Example: https://github.com/kataras/iris/tree/master/_examples/view/context-view-data/ - ViewData(key string, value interface{}) - // GetViewData returns the values registered by `context#ViewData`. - // The return value is `map[string]interface{}`, this means that - // if a custom struct registered to ViewData then this function - // will try to parse it to map, if failed then the return value is nil - // A check for nil is always a good practise if different - // kind of values or no data are registered via `ViewData`. - // - // Similarly to `viewData := ctx.Values().Get("iris.viewData")` or - // `viewData := ctx.Values().Get(ctx.Application().ConfigurationReadOnly().GetViewDataContextKey())`. - GetViewData() map[string]interface{} - // View renders a template based on the registered view engine(s). - // First argument accepts the filename, relative to the view engine's Directory and Extension, - // i.e: if directory is "./templates" and want to render the "./templates/users/index.html" - // then you pass the "users/index.html" as the filename argument. - // - // The second optional argument can receive a single "view model" - // that will be binded to the view template if it's not nil, - // otherwise it will check for previous view data stored by the `ViewData` - // even if stored at any previous handler(middleware) for the same request. - // - // Look .ViewData` and .ViewLayout too. - // - // Examples: https://github.com/kataras/iris/tree/master/_examples/view - View(filename string, optionalViewModel ...interface{}) error - - // Binary writes out the raw bytes as binary data. - Binary(data []byte) (int, error) - // Text writes out a string as plain text. - Text(format string, args ...interface{}) (int, error) - // HTML writes out a string as text/html. - HTML(format string, args ...interface{}) (int, error) - // JSON marshals the given interface object and writes the JSON response. - // If the value is a compatible `proto.Message` one - // then it only uses the options.Proto settings to marshal. - JSON(v interface{}, options ...JSON) (int, error) - // JSONP marshals the given interface object and writes the JSON response. - JSONP(v interface{}, options ...JSONP) (int, error) - // XML marshals the given interface object and writes the XML response. - // To render maps as XML see the `XMLMap` package-level function. - XML(v interface{}, options ...XML) (int, error) - // Problem writes a JSON or XML problem response. - // Order of Problem fields are not always rendered the same. - // - // Behaves exactly like `Context.JSON` - // but with default ProblemOptions.JSON indent of " " and - // a response content type of "application/problem+json" instead. - // - // Use the options.RenderXML and XML fields to change this behavior and - // send a response of content type "application/problem+xml" instead. - // - // Read more at: https://github.com/kataras/iris/wiki/Routing-error-handlers - Problem(v interface{}, opts ...ProblemOptions) (int, error) - // Markdown parses the markdown to html and renders its result to the client. - Markdown(markdownB []byte, options ...Markdown) (int, error) - // YAML parses the "v" using the yaml parser and renders its result to the client. - YAML(v interface{}) (int, error) - // Protobuf parses the "v" of proto Message and renders its result to the client. - Protobuf(v proto.Message) (int, error) - // MsgPack parses the "v" of msgpack format and renders its result to the client. - MsgPack(v interface{}) (int, error) - - // +-----------------------------------------------------------------------+ - // | Content Νegotiation | - // | https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation | | - // +-----------------------------------------------------------------------+ - - // Negotiation creates once and returns the negotiation builder - // to build server-side available content for specific mime type(s) - // and charset(s). - // - // See `Negotiate` method too. - Negotiation() *NegotiationBuilder - // Negotiate used for serving different representations of a resource at the same URI. - // - // The "v" can be a single `N` struct value. - // The "v" can be any value completes the `ContentSelector` interface. - // The "v" can be any value completes the `ContentNegotiator` interface. - // The "v" can be any value of struct(JSON, JSONP, XML, YAML) or - // string(TEXT, HTML) or []byte(Markdown, Binary) or []byte with any matched mime type. - // - // If the "v" is nil, the `Context.Negotitation()` builder's - // content will be used instead, otherwise "v" overrides builder's content - // (server mime types are still retrieved by its registered, supported, mime list) - // - // Set mime type priorities by `Negotiation().JSON().XML().HTML()...`. - // Set charset priorities by `Negotiation().Charset(...)`. - // Set encoding algorithm priorities by `Negotiation().Encoding(...)`. - // Modify the accepted by - // `Negotiation().Accept./Override()/.XML().JSON().Charset(...).Encoding(...)...`. - // - // It returns `ErrContentNotSupported` when not matched mime type(s). - // - // Resources: - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding - // - // Supports the above without quality values. - // - // Read more at: https://github.com/kataras/iris/wiki/Content-negotiation - Negotiate(v interface{}) (int, error) - - // +------------------------------------------------------------+ - // | Serve files | - // +------------------------------------------------------------+ - - // ServeContent replies to the request using the content in the - // provided ReadSeeker. The main benefit of ServeContent over io.Copy - // is that it handles Range requests properly, sets the MIME type, and - // handles If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since, - // and If-Range requests. - // - // If the response's Content-Type header is not set, ServeContent - // first tries to deduce the type from name's file extension. - // - // The name is otherwise unused; in particular it can be empty and is - // never sent in the response. - // - // If modtime is not the zero time or Unix epoch, ServeContent - // includes it in a Last-Modified header in the response. If the - // request includes an If-Modified-Since header, ServeContent uses - // modtime to decide whether the content needs to be sent at all. - // - // The content's Seek method must work: ServeContent uses - // a seek to the end of the content to determine its size. - // - // If the caller has set w's ETag header formatted per RFC 7232, section 2.3, - // ServeContent uses it to handle requests using If-Match, If-None-Match, or If-Range. - // - // Note that *os.File implements the io.ReadSeeker interface. - // Note that gzip compression can be registered through `ctx.Gzip(true)` or `app.Use(iris.Gzip)`. - ServeContent(content io.ReadSeeker, filename string, modtime time.Time) - // ServeContentWithRate same as `ServeContent` but it can throttle the speed of reading - // and though writing the "content" to the client. - ServeContentWithRate(content io.ReadSeeker, filename string, modtime time.Time, limit float64, burst int) - // ServeFile replies to the request with the contents of the named - // file or directory. - // - // If the provided file or directory name is a relative path, it is - // interpreted relative to the current directory and may ascend to - // parent directories. If the provided name is constructed from user - // input, it should be sanitized before calling `ServeFile`. - // - // Use it when you want to serve assets like css and javascript files. - // If client should confirm and save the file use the `SendFile` instead. - // Note that gzip compression can be registered through `ctx.Gzip(true)` or `app.Use(iris.Gzip)`. - ServeFile(filename string) error - // ServeFileWithRate same as `ServeFile` but it can throttle the speed of reading - // and though writing the file to the client. - ServeFileWithRate(filename string, limit float64, burst int) error - // SendFile sends a file as an attachment, that is downloaded and saved locally from client. - // Note that gzip compression can be registered through `ctx.Gzip(true)` or `app.Use(iris.Gzip)`. - // Use `ServeFile` if a file should be served as a page asset instead. - SendFile(filename string, destinationName string) error - // SendFileWithRate same as `SendFile` but it can throttle the speed of reading - // and though writing the file to the client. - SendFileWithRate(src, destName string, limit float64, burst int) error - - // +------------------------------------------------------------+ - // | Cookies | - // +------------------------------------------------------------+ - - // AddCookieOptions adds cookie options for `SetCookie`, - // `SetCookieKV, UpsertCookie` and `RemoveCookie` methods - // for the current request. It can be called from a middleware before - // cookies sent or received from the next Handler in the chain. - // See `ClearCookieOptions` too. - // - // Available builtin Cookie options are: - // * CookieAllowReclaim - // * CookieAllowSubdomains - // * CookieSecure - // * CookieHTTPOnly - // * CookieSameSite - // * CookiePath - // * CookieCleanPath - // * CookieExpires - // * CookieEncoding - // - // Example at: https://github.com/kataras/iris/tree/master/_examples/cookies/securecookie - AddCookieOptions(options ...CookieOption) - // ClearCookieOptions clears any previously registered cookie options. - // See `AddCookieOptions` too. - ClearCookieOptions() - // SetCookie adds a cookie. - // Use of the "options" is not required, they can be used to amend the "cookie". - // - // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic - SetCookie(cookie *http.Cookie, options ...CookieOption) - // UpsertCookie adds a cookie to the response like `SetCookie` does - // but it will also perform a replacement of the cookie - // if already set by a previous `SetCookie` call. - // It reports whether the cookie is new (true) or an existing one was updated (false). - UpsertCookie(cookie *http.Cookie, options ...CookieOption) bool - // SetCookieKV adds a cookie, requires the name(string) and the value(string). - // - // By default it expires at 2 hours and it's added to the root path, - // use the `CookieExpires` and `CookiePath` to modify them. - // Alternatively: ctx.SetCookie(&http.Cookie{...}) - // - // If you want to set custom the path: - // ctx.SetCookieKV(name, value, iris.CookiePath("/custom/path/cookie/will/be/stored")) - // - // If you want to be visible only to current request path: - // ctx.SetCookieKV(name, value, iris.CookieCleanPath/iris.CookiePath("")) - // More: - // iris.CookieExpires(time.Duration) - // iris.CookieHTTPOnly(false) - // - // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic - SetCookieKV(name, value string, options ...CookieOption) - // GetCookie returns cookie's value by its name - // returns empty string if nothing was found. - // - // If you want more than the value then: - // cookie, err := ctx.Request().Cookie("name") - // - // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic - GetCookie(name string, options ...CookieOption) string - // RemoveCookie deletes a cookie by its name and path = "/". - // Tip: change the cookie's path to the current one by: RemoveCookie("name", iris.CookieCleanPath) - // - // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic - RemoveCookie(name string, options ...CookieOption) - // VisitAllCookies accepts a visitor function which is called - // on each (request's) cookies' name and value. - VisitAllCookies(visitor func(name string, value string)) - - // MaxAge returns the "cache-control" request header's value - // seconds as int64 - // if header not found or parse failed then it returns -1. - MaxAge() int64 - - // +------------------------------------------------------------+ - // | Advanced: Response Recorder and Transactions | - // +------------------------------------------------------------+ - - // Record transforms the context's basic and direct responseWriter to a ResponseRecorder - // which can be used to reset the body, reset headers, get the body, - // get & set the status code at any time and more. - Record() - // Recorder returns the context's ResponseRecorder - // if not recording then it starts recording and returns the new context's ResponseRecorder - Recorder() *ResponseRecorder - // IsRecording returns the response recorder and a true value - // when the response writer is recording the status code, body, headers and so on, - // else returns nil and false. - IsRecording() (*ResponseRecorder, bool) - - // BeginTransaction starts a scoped transaction. - // - // You can search third-party articles or books on how Business Transaction works (it's quite simple, especially here). - // - // Note that this is unique and new - // (=I haver never seen any other examples or code in Golang on this subject, so far, as with the most of iris features...) - // it's not covers all paths, - // such as databases, this should be managed by the libraries you use to make your database connection, - // this transaction scope is only for context's response. - // Transactions have their own middleware ecosystem also, look iris.go:UseTransaction. - // - // See https://github.com/kataras/iris/tree/master/_examples/ for more - BeginTransaction(pipe func(t *Transaction)) - // SkipTransactions if called then skip the rest of the transactions - // or all of them if called before the first transaction - SkipTransactions() - // TransactionsSkipped returns true if the transactions skipped or canceled at all. - TransactionsSkipped() bool - - // Exec calls the `context/Application#ServeCtx` - // based on this context but with a changed method and path - // like it was requested by the user, but it is not. - // - // Offline means that the route is registered to the iris and have all features that a normal route has - // BUT it isn't available by browsing, its handlers executed only when other handler's context call them - // it can validate paths, has sessions, path parameters and all. - // - // You can find the Route by app.GetRoute("theRouteName") - // you can set a route name as: myRoute := app.Get("/mypath", handler)("theRouteName") - // that will set a name to the route and returns its RouteInfo instance for further usage. - // - // It doesn't changes the global state, if a route was "offline" it remains offline. - // - // app.None(...) and app.GetRoutes().Offline(route)/.Online(route, method) - // - // Example: https://github.com/kataras/iris/tree/master/_examples/routing/route-state - // - // User can get the response by simple using rec := ctx.Recorder(); rec.Body()/rec.StatusCode()/rec.Header(). - // - // Context's Values and the Session are kept in order to be able to communicate via the result route. - // - // It's for extreme use cases, 99% of the times will never be useful for you. - Exec(method, path string) - - // RouteExists reports whether a particular route exists - // It will search from the current subdomain of context's host, if not inside the root domain. - RouteExists(method, path string) bool - - // ReflectValue caches and returns a []reflect.Value{reflect.ValueOf(ctx)}. - // It's just a helper to maintain variable inside the context itself. - ReflectValue() []reflect.Value - // Controller returns a reflect Value of the custom Controller from which this handler executed. - // It will return a Kind() == reflect.Invalid if the handler was not executed from within a controller. - Controller() reflect.Value - // RegisterDependency registers a struct or slice - // or pointer to struct dependency at request-time - // for the next handler in the chain. One value per type. - // Note that it's highly recommended to register - // your dependencies before server ran - // through Party.ConfigureContainer or mvc.Application.Register - // in sake of minimum performance cost. - // - // See `UnregisterDependency` too. - RegisterDependency(v interface{}) - // UnregisterDependency removes a dependency based on its type. - // Reports whether a dependency with that type was found and removed successfully. - // - // See `RegisterDependency` too. - UnregisterDependency(typ reflect.Type) bool - - // Application returns the iris app instance which belongs to this context. - // Worth to notice that this function returns an interface - // of the Application, which contains methods that are safe - // to be executed at serve-time. The full app's fields - // and methods are not available here for the developer's safety. - Application() Application - - // SetID sets an ID, any value, to the Request Context. - // If possible the "id" should implement a `String() string` method - // so it can be rendered on `Context.String` method. - // - // See `GetID` and `middleware/requestid` too. - SetID(id interface{}) - // GetID returns the Request Context's ID. - // It returns nil if not given by a prior `SetID` call. - // See `middleware/requestid` too. - GetID() interface{} - // String returns the string representation of this request. - // - // It returns the Context's ID given by a `SetID`call, - // followed by the client's IP and the method:uri. - String() string -} - -var _ Context = (*context)(nil) - -// Do calls the SetHandlers(handlers) -// and executes the first handler, -// handlers should not be empty. -// -// It's used by the router, developers may use that -// to replace and execute handlers immediately. -func Do(ctx Context, handlers Handlers) { - if len(handlers) > 0 { - ctx.SetHandlers(handlers) - handlers[0](ctx) - } -} - // LimitRequestBodySize is a middleware which sets a request body size limit // for all next handlers in the chain. var LimitRequestBodySize = func(maxRequestBodySizeBytes int64) Handler { - return func(ctx Context) { + return func(ctx *Context) { ctx.SetMaxRequestBodySize(maxRequestBodySizeBytes) ctx.Next() } } -// Gzip is a middleware which enables writing -// using gzip compression, if client supports. -var Gzip = func(ctx Context) { - ctx.Gzip(true) - ctx.Next() -} - -// GzipReader is a middleware which enables gzip decompression, -// when client sends gzip compressed data. -// -// Similar to: func(ctx iris.Context) { -// ctx.GzipReader(true) -// ctx.Next() -// } -var GzipReader = func(ctx Context) { - ctx.GzipReader(true) - ctx.Next() -} - // Map is just a type alias of the map[string]interface{} type. type Map = map[string]interface{} -// +------------------------------------------------------------+ -// | Context Implementation | -// +------------------------------------------------------------+ - -type context struct { +// Context is the midle-man server's "object" dealing with incoming requests. +// +// A New context is being acquired from a sync.Pool on each connection. +// The Context is the most important thing on the iris's http flow. +// +// Developers send responses to the client's request through a Context. +// Developers get request information from the client's request a Context. +type Context struct { // the http.ResponseWriter wrapped by custom writer. writer ResponseWriter // the original http.Request @@ -1276,13 +124,9 @@ type context struct { currentHandlerIndex int } -// NewContext returns the default, internal, context implementation. -// You may use this function to embed the default context implementation -// to a custom one. -// -// This context is received by the context pool. -func NewContext(app Application) Context { - return &context{app: app} +// NewContext returns a new Context instance. +func NewContext(app Application) *Context { + return &Context{app: app} } // Clone returns a copy of the context that @@ -1291,14 +135,14 @@ func NewContext(app Application) Context { // 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 { +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{ + return &Context{ app: ctx.app, values: valuesCopy, params: RequestParams{Store: paramsCopy}, @@ -1319,7 +163,7 @@ func (ctx *context) Clone() Context { // 3. the defer function. // 4. response writer to the http.ResponseWriter. // 5. request to the *http.Request. -func (ctx *context) BeginRequest(w http.ResponseWriter, r *http.Request) { +func (ctx *Context) BeginRequest(w http.ResponseWriter, r *http.Request) { ctx.currentRoute = nil ctx.handlers = nil // will be filled by router.Serve/HTTP ctx.values = ctx.values[0:0] // >> >> by context.Values().Set @@ -1336,7 +180,7 @@ func (ctx *context) BeginRequest(w http.ResponseWriter, r *http.Request) { // 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() { +func (ctx *Context) EndRequest() { if !ctx.app.ConfigurationReadOnly().GetDisableAutoFireStatusCode() && StatusCodeNotSuccessful(ctx.GetStatusCode()) { ctx.app.FireErrorCode(ctx) @@ -1350,7 +194,7 @@ func (ctx *context) EndRequest() { // 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 { +func (ctx *Context) IsCanceled() bool { if reqCtx := ctx.request.Context(); reqCtx != nil { err := reqCtx.Err() if err != nil && errors.Is(err, stdContext.Canceled) { @@ -1380,7 +224,7 @@ func (ctx *context) IsCanceled() bool { // Note that you can register only one callback per route. // // See `OnClose` too. -func (ctx *context) OnConnectionClose(cb Handler) bool { +func (ctx *Context) OnConnectionClose(cb Handler) bool { if cb == nil { return false } @@ -1413,7 +257,7 @@ func (ctx *context) OnConnectionClose(cb Handler) bool { // on the handler's routine itself (Context access). // // See `OnConnectionClose` too. -func (ctx *context) OnClose(cb Handler) { +func (ctx *Context) OnClose(cb Handler) { if cb == nil { return } @@ -1430,7 +274,7 @@ func (ctx *context) OnClose(cb Handler) { // by simple boolean check or an atomic one. var executed uint32 - callback := func(ctx Context) { + callback := func(ctx *Context) { if atomic.CompareAndSwapUint32(&executed, 0, 1) { cb(ctx) } @@ -1458,7 +302,7 @@ var acquireGoroutines = func() interface{} { return &goroutines{wg: new(sync.WaitGroup)} } -func (ctx *context) Go(fn func(cancelCtx stdContext.Context)) (running int) { +func (ctx *Context) Go(fn func(cancelCtx stdContext.Context)) (running int) { g := ctx.Values().GetOrSet(goroutinesContextKey, acquireGoroutines).(*goroutines) if fn != nil { g.wg.Add(1) @@ -1487,17 +331,17 @@ func (ctx *context) Go(fn func(cancelCtx stdContext.Context)) (running int) { */ // ResponseWriter returns an http.ResponseWriter compatible response writer, as expected. -func (ctx *context) ResponseWriter() ResponseWriter { +func (ctx *Context) ResponseWriter() ResponseWriter { return ctx.writer } // ResetResponseWriter should change or upgrade the context's ResponseWriter. -func (ctx *context) ResetResponseWriter(newResponseWriter ResponseWriter) { +func (ctx *Context) ResetResponseWriter(newResponseWriter ResponseWriter) { ctx.writer = newResponseWriter } // Request returns the original *http.Request, as expected. -func (ctx *context) Request() *http.Request { +func (ctx *Context) Request() *http.Request { return ctx.request } @@ -1512,32 +356,37 @@ func (ctx *context) Request() *http.Request { // r := ctx.Request() // stdCtx := context.WithValue(r.Context(), key, val) // ctx.ResetRequest(r.WithContext(stdCtx)). -func (ctx *context) ResetRequest(r *http.Request) { +func (ctx *Context) ResetRequest(r *http.Request) { ctx.request = r } -// SetCurrentRoutes sets the route internally, +// SetCurrentRoute sets the route internally, // See `GetCurrentRoute()` method too. // It's being initialized by the Router. // See `Exec` or `SetHandlers/AddHandler` methods to simulate a request. -func (ctx *context) SetCurrentRoute(route RouteReadOnly) { +func (ctx *Context) SetCurrentRoute(route RouteReadOnly) { ctx.currentRoute = route } // GetCurrentRoute returns the current "read-only" route that // was registered to this request's path. -func (ctx *context) GetCurrentRoute() RouteReadOnly { +func (ctx *Context) GetCurrentRoute() RouteReadOnly { return ctx.currentRoute } -// Do calls the SetHandlers(handlers) +// Do sets the "handlers" as the chain // and executes the first handler, // handlers should not be empty. // // It's used by the router, developers may use that // to replace and execute handlers immediately. -func (ctx *context) Do(handlers Handlers) { - Do(ctx, handlers) +func (ctx *Context) Do(handlers Handlers) { + if len(handlers) == 0 { + return + } + + ctx.handlers = handlers + handlers[0](ctx) } // AddHandler can add handler(s) @@ -1548,17 +397,17 @@ func (ctx *context) Do(handlers Handlers) { // If AddHandler called then the handlers will be inserted // to the end of the already-defined route's handler. // -func (ctx *context) AddHandler(handlers ...Handler) { +func (ctx *Context) AddHandler(handlers ...Handler) { ctx.handlers = append(ctx.handlers, handlers...) } // SetHandlers replaces all handlers with the new. -func (ctx *context) SetHandlers(handlers Handlers) { +func (ctx *Context) SetHandlers(handlers Handlers) { ctx.handlers = handlers } // Handlers keeps tracking of the current handlers. -func (ctx *context) Handlers() Handlers { +func (ctx *Context) Handlers() Handlers { return ctx.handlers } @@ -1568,7 +417,7 @@ func (ctx *context) Handlers() Handlers { // current handler index without change the current index. // // Look Handlers(), Next() and StopExecution() too. -func (ctx *context) HandlerIndex(n int) (currentIndex int) { +func (ctx *Context) HandlerIndex(n int) (currentIndex int) { if n < 0 || n > len(ctx.handlers)-1 { return ctx.currentHandlerIndex } @@ -1613,7 +462,7 @@ func (ctx *context) HandlerIndex(n int) (currentIndex int) { // return c.Service.GetAll() //} // Alternative way is `!ctx.IsStopped()` if middleware make use of the `ctx.StopExecution()` on failure. -func (ctx *context) Proceed(h Handler) bool { +func (ctx *Context) Proceed(h Handler) bool { beforeIdx := ctx.currentHandlerIndex h(ctx) if ctx.currentHandlerIndex > beforeIdx && !ctx.IsStopped() { @@ -1623,19 +472,19 @@ func (ctx *context) Proceed(h Handler) bool { } // HandlerName returns the current handler's name, helpful for debugging. -func (ctx *context) HandlerName() string { +func (ctx *Context) HandlerName() string { return HandlerName(ctx.handlers[ctx.currentHandlerIndex]) } // HandlerFileLine returns the current running handler's function source file and line information. // Useful mostly when debugging. -func (ctx *context) HandlerFileLine() (file string, line int) { +func (ctx *Context) HandlerFileLine() (file string, line int) { return HandlerFileLine(ctx.handlers[ctx.currentHandlerIndex]) } // RouteName returns the route name that this handler is running on. // Note that it may return empty on not found handlers. -func (ctx *context) RouteName() string { +func (ctx *Context) RouteName() string { if ctx.currentRoute == nil { return "" } @@ -1643,40 +492,20 @@ func (ctx *context) RouteName() string { return ctx.currentRoute.Name() } -// Next is the function that executed when `ctx.Next()` is called. -// It can be changed to a customized one if needed (very advanced usage). -// -// See `DefaultNext` for more information about this and why it's exported like this. -var Next = DefaultNext - -// DefaultNext is the default function that executed on each middleware if `ctx.Next()` -// is called. -// -// DefaultNext calls the next handler from the handlers chain by registration order, +// Next calls the next handler from the handlers chain, // it should be used inside a middleware. -// -// It can be changed to a customized one if needed (very advanced usage). -// -// Developers are free to customize the whole or part of the Context's implementation -// by implementing a new `context.Context` (see https://github.com/kataras/iris/tree/master/_examples/routing/custom-context) -// or by just override the `context.Next` package-level field, `context.DefaultNext` is exported -// in order to be able for developers to merge your customized version one with the default behavior as well. -func DefaultNext(ctx Context) { +func (ctx *Context) Next() { if ctx.IsStopped() { return } - if n, handlers := ctx.HandlerIndex(-1)+1, ctx.Handlers(); n < len(handlers) { - ctx.HandlerIndex(n) - handlers[n](ctx) - } -} -// Next calls all the next handler from the handlers chain, -// it should be used inside a middleware. -// -// Note: Custom context should override this method in order to be able to pass its own context.Context implementation. -func (ctx *context) Next() { - Next(ctx) + nextIndex := ctx.currentHandlerIndex + 1 + handlers := ctx.handlers + + if nextIndex < len(handlers) { + ctx.currentHandlerIndex = nextIndex + handlers[nextIndex](ctx) + } } // NextOr checks if chain has a next handler, if so then it executes it @@ -1687,7 +516,7 @@ func (ctx *context) Next() { // // Note that if no next handler found and handlers are missing then // it sends a Status Not Found (404) to the client and it stops the execution. -func (ctx *context) NextOr(handlers ...Handler) bool { +func (ctx *Context) NextOr(handlers ...Handler) bool { if next := ctx.NextHandler(); next != nil { ctx.Skip() // skip this handler from the chain. next(ctx) @@ -1709,12 +538,12 @@ func (ctx *context) NextOr(handlers ...Handler) bool { // otherwise it sends a Status Not Found (404) to the client and stops the execution. // // Returns true if next handler exists and executed, otherwise false. -func (ctx *context) NextOrNotFound() bool { return ctx.NextOr() } +func (ctx *Context) NextOrNotFound() bool { return ctx.NextOr() } // NextHandler returns (it doesn't execute) the next handler from the handlers chain. // // Use .Skip() to skip this handler if needed to execute the next of this returning handler. -func (ctx *context) NextHandler() Handler { +func (ctx *Context) NextHandler() Handler { if ctx.IsStopped() { return nil } @@ -1728,7 +557,7 @@ func (ctx *context) NextHandler() Handler { // Skip skips/ignores the next handler from the handlers chain, // it should be used inside a middleware. -func (ctx *context) Skip() { +func (ctx *Context) Skip() { ctx.HandlerIndex(ctx.currentHandlerIndex + 1) } @@ -1737,13 +566,13 @@ const stopExecutionIndex = -1 // I don't set to a max value because we want to b // StopExecution stops the handlers chain of this request. // Meaning that any following `Next` calls are ignored, // as a result the next handlers in the chain will not be fire. -func (ctx *context) StopExecution() { +func (ctx *Context) StopExecution() { ctx.currentHandlerIndex = stopExecutionIndex } // IsStopped reports whether the current position of the context's handlers is -1, // means that the StopExecution() was called at least once. -func (ctx *context) IsStopped() bool { +func (ctx *Context) IsStopped() bool { return ctx.currentHandlerIndex == stopExecutionIndex } @@ -1751,7 +580,7 @@ func (ctx *context) IsStopped() bool { // // If the status code is a failure one then // it will also fire the specified error code handler. -func (ctx *context) StopWithStatus(statusCode int) { +func (ctx *Context) StopWithStatus(statusCode int) { ctx.StopExecution() ctx.StatusCode(statusCode) } @@ -1761,7 +590,7 @@ func (ctx *context) StopWithStatus(statusCode int) { // // If the status code is a failure one then // it will also fire the specified error code handler. -func (ctx *context) StopWithText(statusCode int, plainText string) { +func (ctx *Context) StopWithText(statusCode int, plainText string) { ctx.StopWithStatus(statusCode) ctx.WriteString(plainText) } @@ -1771,7 +600,7 @@ func (ctx *context) StopWithText(statusCode int, plainText string) { // // If the status code is a failure one then // it will also fire the specified error code handler. -func (ctx *context) StopWithError(statusCode int, err error) { +func (ctx *Context) StopWithError(statusCode int, err error) { if err == nil { return } @@ -1784,7 +613,7 @@ func (ctx *context) StopWithError(statusCode int, err error) { // // If the status code is a failure one then // it will also fire the specified error code handler. -func (ctx *context) StopWithJSON(statusCode int, jsonObject interface{}) { +func (ctx *Context) StopWithJSON(statusCode int, jsonObject interface{}) { ctx.StopWithStatus(statusCode) ctx.JSON(jsonObject) } @@ -1795,7 +624,7 @@ func (ctx *context) StopWithJSON(statusCode int, jsonObject interface{}) { // // If the status code is a failure one then // it will also fire the specified error code handler. -func (ctx *context) StopWithProblem(statusCode int, problem Problem) { +func (ctx *Context) StopWithProblem(statusCode int, problem Problem) { ctx.StopWithStatus(statusCode) problem.Status(statusCode) ctx.Problem(problem) @@ -1810,7 +639,7 @@ func (ctx *context) StopWithProblem(statusCode int, problem Problem) { // Params returns the current url's named parameters key-value storage. // Named path parameters are being saved here. // This storage, as the whole context, is per-request lifetime. -func (ctx *context) Params() *RequestParams { +func (ctx *Context) Params() *RequestParams { return &ctx.params } @@ -1820,7 +649,7 @@ func (ctx *context) Params() *RequestParams { // // You can use this function to Set and Get local values // that can be used to share information between handlers and middleware. -func (ctx *context) Values() *memstore.Store { +func (ctx *Context) Values() *memstore.Store { return &ctx.values } @@ -1829,13 +658,13 @@ func (ctx *context) Values() *memstore.Store { // +------------------------------------------------------------+ // Method returns the request.Method, the client's http method to the server. -func (ctx *context) Method() string { +func (ctx *Context) Method() string { return ctx.request.Method } // Path returns the full request path, // escaped if EnablePathEscape config field is true. -func (ctx *context) Path() string { +func (ctx *Context) Path() string { return ctx.RequestPath(ctx.app.ConfigurationReadOnly().GetEnablePathEscape()) } @@ -1872,7 +701,7 @@ func DecodeURL(uri string) string { // RequestPath returns the full request path, // based on the 'escape'. -func (ctx *context) RequestPath(escape bool) string { +func (ctx *Context) RequestPath(escape bool) string { if escape { return ctx.request.URL.EscapedPath() // DecodeQuery(ctx.request.URL.EscapedPath()) } @@ -1883,7 +712,7 @@ func (ctx *context) RequestPath(escape bool) string { // PathPrefixMap accepts a map of string and a handler. // The key of "m" is the key, which is the prefix, regular expressions are not valid. // The value of "m" is the handler that will be executed if HasPrefix(context.Path). -// func (ctx *context) PathPrefixMap(m map[string]context.Handler) bool { +// func (ctx *Context) PathPrefixMap(m map[string]context.Handler) bool { // path := ctx.Path() // for k, v := range m { // if strings.HasPrefix(path, k) { @@ -1896,7 +725,7 @@ func (ctx *context) RequestPath(escape bool) string { // Host returns the host part of the current URI. // This method makes use of the `Configuration.HostProxyHeaders` field too. -func (ctx *context) Host() string { +func (ctx *Context) Host() string { for header, ok := range ctx.app.ConfigurationReadOnly().GetHostProxyHeaders() { if !ok { continue @@ -1921,7 +750,7 @@ func GetHost(r *http.Request) string { // Subdomain returns the subdomain of this request, if any. // Note that this is a fast method which does not cover all cases. -func (ctx *context) Subdomain() (subdomain string) { +func (ctx *Context) Subdomain() (subdomain string) { host := ctx.Host() if index := strings.IndexByte(host, '.'); index > 0 { subdomain = host[0:index] @@ -1942,12 +771,12 @@ func (ctx *context) Subdomain() (subdomain string) { // // Order may change. // Example: https://github.com/kataras/iris/tree/master/_examples/routing/intelligence/manual -func (ctx *context) FindClosest(n int) []string { +func (ctx *Context) FindClosest(n int) []string { return ctx.app.FindClosestPaths(ctx.Subdomain(), ctx.Path(), n) } // IsWWW returns true if the current subdomain (if any) is www. -func (ctx *context) IsWWW() bool { +func (ctx *Context) IsWWW() bool { host := ctx.Host() if index := strings.IndexByte(host, '.'); index > 0 { // if it has a subdomain and it's www then return true. @@ -1958,9 +787,9 @@ func (ctx *context) IsWWW() bool { return false } -// FullRqeuestURI returns the full URI, +// FullRequestURI returns the full URI, // including the scheme, the host and the relative requested path/resource. -func (ctx *context) FullRequestURI() string { +func (ctx *Context) FullRequestURI() string { return ctx.AbsoluteURI(ctx.Path()) } @@ -1975,7 +804,7 @@ func (ctx *context) FullRequestURI() string { // `Configuration.WithRemoteAddrHeader(...)`, // `Configuration.WithoutRemoteAddrHeader(...)` and // `Configuration.RemoteAddrPrivateSubnets` for more. -func (ctx *context) RemoteAddr() string { +func (ctx *Context) RemoteAddr() string { remoteHeaders := ctx.app.ConfigurationReadOnly().GetRemoteAddrHeaders() privateSubnets := ctx.app.ConfigurationReadOnly().GetRemoteAddrPrivateSubnets() @@ -2012,12 +841,12 @@ func TrimHeaderValue(v string) string { } // GetHeader returns the request header's value based on its name. -func (ctx *context) GetHeader(name string) string { +func (ctx *Context) GetHeader(name string) string { return ctx.request.Header.Get(name) } // GetDomain resolves and returns the server's domain. -func (ctx *context) GetDomain() string { +func (ctx *Context) GetDomain() string { hostport := ctx.Host() if host, _, err := net.SplitHostPort(hostport); err == nil { // has port. @@ -2051,7 +880,7 @@ func (ctx *context) GetDomain() string { // // Read more at: https://developer.mozilla.org/en-US/docs/AJAX // and https://xhr.spec.whatwg.org/ -func (ctx *context) IsAjax() bool { +func (ctx *Context) IsAjax() bool { return ctx.GetHeader("X-Requested-With") == "XMLHttpRequest" } @@ -2062,7 +891,7 @@ var isMobileRegex = regexp.MustCompile("(?:hpw|i|web)os|alamofire|alcatel|amoi|a // device to communicate with the server, otherwise false. // // Keep note that this checks the "User-Agent" request header. -func (ctx *context) IsMobile() bool { +func (ctx *Context) IsMobile() bool { s := strings.ToLower(ctx.GetHeader("User-Agent")) return isMobileRegex.MatchString(s) } @@ -2070,7 +899,7 @@ func (ctx *context) IsMobile() bool { var isScriptRegex = regexp.MustCompile("curl|wget|collectd|python|urllib|java|jakarta|httpclient|phpcrawl|libwww|perl|go-http|okhttp|lua-resty|winhttp|awesomium") // IsScript reports whether a client is a script. -func (ctx *context) IsScript() bool { +func (ctx *Context) IsScript() bool { s := strings.ToLower(ctx.GetHeader("User-Agent")) return isScriptRegex.MatchString(s) } @@ -2078,7 +907,7 @@ func (ctx *context) IsScript() bool { // IsSSL reports whether the client is running under HTTPS SSL. // // See `IsHTTP2` too. -func (ctx *context) IsSSL() bool { +func (ctx *Context) IsSSL() bool { ssl := strings.EqualFold(ctx.request.URL.Scheme, "https") || ctx.request.TLS != nil if !ssl { for k, v := range ctx.app.ConfigurationReadOnly().GetSSLProxyHeaders() { @@ -2095,12 +924,12 @@ func (ctx *context) IsSSL() bool { // The client code always uses either HTTP/1.1 or HTTP/2. // // See `IsSSL` too. -func (ctx *context) IsHTTP2() bool { +func (ctx *Context) IsHTTP2() bool { return ctx.request.ProtoMajor == 2 } // IsGRPC reports whether the request came from a gRPC client. -func (ctx *context) IsGRPC() bool { +func (ctx *Context) IsGRPC() bool { return ctx.IsHTTP2() && ctx.GetContentTypeRequested() == ContentGRPCHeaderValue } @@ -2147,7 +976,7 @@ var emptyReferrer = Referrer{Type: ReferrerInvalid, GoogleType: ReferrerNotGoogl // GetReferrer extracts and returns the information from the "Referer" (or "Referrer") header // and url query parameter as specified in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy. -func (ctx *context) GetReferrer() Referrer { +func (ctx *Context) GetReferrer() Referrer { // the underline net/http follows the https://tools.ietf.org/html/rfc7231#section-5.5.2, // so there is nothing special left to do. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy @@ -2190,13 +1019,13 @@ func (ctx *context) GetReferrer() Referrer { // the default language will be set instead. // // See `i18n.ExtractFunc` for a more organised way of the same feature. -func (ctx *context) SetLanguage(langCode string) { +func (ctx *Context) SetLanguage(langCode string) { ctx.values.Set(ctx.app.ConfigurationReadOnly().GetLanguageContextKey(), langCode) } // GetLocale returns the current request's `Locale` found by i18n middleware. // See `Tr` too. -func (ctx *context) GetLocale() Locale { +func (ctx *Context) GetLocale() Locale { // Cache the Locale itself for multiple calls of `Tr` method. contextKey := ctx.app.ConfigurationReadOnly().GetLocaleContextKey() if v := ctx.values.Get(contextKey); v != nil { @@ -2217,12 +1046,12 @@ func (ctx *context) GetLocale() Locale { // See `GetLocale` too. // // Example: https://github.com/kataras/iris/tree/master/_examples/i18n -func (ctx *context) Tr(format string, args ...interface{}) string { // other name could be: Localize. +func (ctx *Context) Tr(message string, values ...interface{}) string { // other name could be: Localize. if locale := ctx.GetLocale(); locale != nil { // TODO: here... I need to change the logic, if not found then call the i18n's get locale and set the value in order to be fastest on routes that are not using (no need to reigster a middleware.) - return locale.GetMessage(format, args...) + return locale.GetMessage(message, values...) } - return fmt.Sprintf(format, args...) + return message } // +------------------------------------------------------------+ @@ -2231,7 +1060,7 @@ func (ctx *context) Tr(format string, args ...interface{}) string { // other nam // Header adds a header to the response, if value is empty // it removes the header by its name. -func (ctx *context) Header(name string, value string) { +func (ctx *Context) Header(name string, value string) { if value == "" { ctx.writer.Header().Del(name) return @@ -2242,10 +1071,23 @@ func (ctx *context) Header(name string, value string) { const contentTypeContextKey = "iris.content_type" func shouldAppendCharset(cType string) bool { - return cType != ContentBinaryHeaderValue && cType != ContentWebassemblyHeaderValue + if idx := strings.IndexRune(cType, '/'); idx > 1 && len(cType) > idx+1 { + typ := cType[0:idx] + if typ == "application" { + switch cType[idx+1:] { + case "json", "xml", "yaml", "problem+json", "problem+xml": + return true + default: + return false + } + } + + } + + return true } -func (ctx *context) contentTypeOnce(cType string, charset string) { +func (ctx *Context) contentTypeOnce(cType string, charset string) { if charset == "" { charset = ctx.app.ConfigurationReadOnly().GetCharset() } @@ -2260,7 +1102,7 @@ func (ctx *context) contentTypeOnce(cType string, charset string) { // ContentType sets the response writer's // header "Content-Type" to the 'cType'. -func (ctx *context) ContentType(cType string) { +func (ctx *Context) ContentType(cType string) { if cType == "" { return } @@ -2287,20 +1129,20 @@ func (ctx *context) ContentType(cType string) { // GetContentType returns the response writer's // header value of "Content-Type". -func (ctx *context) GetContentType() string { +func (ctx *Context) GetContentType() string { return ctx.writer.Header().Get(ContentTypeHeaderKey) } -// GetContentType returns the request's +// GetContentTypeRequested returns the request's // trim-ed(without the charset and priority values) // header value of "Content-Type". -func (ctx *context) GetContentTypeRequested() string { +func (ctx *Context) GetContentTypeRequested() string { return TrimHeaderValue(ctx.GetHeader(ContentTypeHeaderKey)) } // GetContentLength returns the request's // header value of "Content-Length". -func (ctx *context) GetContentLength() int64 { +func (ctx *Context) GetContentLength() int64 { if v := ctx.GetHeader(ContentLengthHeaderKey); v != "" { n, _ := strconv.ParseInt(v, 10, 64) return n @@ -2312,7 +1154,7 @@ func (ctx *context) GetContentLength() int64 { // Look .GetStatusCode & .FireStatusCode too. // // Remember, the last one before .Write matters except recorder and transactions. -func (ctx *context) StatusCode(statusCode int) { +func (ctx *Context) StatusCode(statusCode int) { ctx.writer.WriteHeader(statusCode) } @@ -2321,14 +1163,14 @@ func (ctx *context) StatusCode(statusCode int) { // to be executed. Next handlers are being executed on iris because you can alt the // error code and change it to a more specific one, i.e // users := app.Party("/users") -// users.Done(func(ctx context.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }}) -func (ctx *context) NotFound() { +// users.Done(func(ctx iris.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }}) +func (ctx *Context) NotFound() { ctx.StatusCode(http.StatusNotFound) } // GetStatusCode returns the current status code of the response. // Look StatusCode too. -func (ctx *context) GetStatusCode() int { +func (ctx *Context) GetStatusCode() int { return ctx.writer.StatusCode() } @@ -2336,8 +1178,8 @@ func (ctx *context) GetStatusCode() int { // | Various Request and Post Data | // +------------------------------------------------------------+ -// URLParam returns true if the url parameter exists, otherwise false. -func (ctx *context) URLParamExists(name string) bool { +// URLParamExists returns true if the url parameter exists, otherwise false. +func (ctx *Context) URLParamExists(name string) bool { if q := ctx.request.URL.Query(); q != nil { _, exists := q[name] return exists @@ -2347,7 +1189,7 @@ func (ctx *context) URLParamExists(name string) bool { } // URLParamDefault returns the get parameter from a request, if not found then "def" is returned. -func (ctx *context) URLParamDefault(name string, def string) string { +func (ctx *Context) URLParamDefault(name string, def string) string { if v := ctx.request.URL.Query().Get(name); v != "" { return v } @@ -2356,17 +1198,17 @@ func (ctx *context) URLParamDefault(name string, def string) string { } // URLParam returns the get parameter from a request, if any. -func (ctx *context) URLParam(name string) string { +func (ctx *Context) URLParam(name string) string { return ctx.URLParamDefault(name, "") } // URLParamTrim returns the url query parameter with trailing white spaces removed from a request. -func (ctx *context) URLParamTrim(name string) string { +func (ctx *Context) URLParamTrim(name string) string { return strings.TrimSpace(ctx.URLParam(name)) } // URLParamEscape returns the escaped url query parameter from a request. -func (ctx *context) URLParamEscape(name string) string { +func (ctx *Context) URLParamEscape(name string) string { return DecodeQuery(ctx.URLParam(name)) } @@ -2385,7 +1227,7 @@ var ErrNotFound = errors.New("not found") // URLParamInt returns the url query parameter as int value from a request, // returns -1 and an error if parse failed or not found. -func (ctx *context) URLParamInt(name string) (int, error) { +func (ctx *Context) URLParamInt(name string) (int, error) { if v := ctx.URLParam(name); v != "" { n, err := strconv.Atoi(v) if err != nil { @@ -2399,7 +1241,7 @@ func (ctx *context) URLParamInt(name string) (int, error) { // URLParamIntDefault returns the url query parameter as int value from a request, // if not found or parse failed then "def" is returned. -func (ctx *context) URLParamIntDefault(name string, def int) int { +func (ctx *Context) URLParamIntDefault(name string, def int) int { v, err := ctx.URLParamInt(name) if err != nil { return def @@ -2410,7 +1252,7 @@ func (ctx *context) URLParamIntDefault(name string, def int) int { // URLParamInt32Default returns the url query parameter as int32 value from a request, // if not found or parse failed then "def" is returned. -func (ctx *context) URLParamInt32Default(name string, def int32) int32 { +func (ctx *Context) URLParamInt32Default(name string, def int32) int32 { if v := ctx.URLParam(name); v != "" { n, err := strconv.ParseInt(v, 10, 32) if err != nil { @@ -2425,7 +1267,7 @@ func (ctx *context) URLParamInt32Default(name string, def int32) int32 { // URLParamInt64 returns the url query parameter as int64 value from a request, // returns -1 and an error if parse failed or not found. -func (ctx *context) URLParamInt64(name string) (int64, error) { +func (ctx *Context) URLParamInt64(name string) (int64, error) { if v := ctx.URLParam(name); v != "" { n, err := strconv.ParseInt(v, 10, 64) if err != nil { @@ -2439,7 +1281,7 @@ func (ctx *context) URLParamInt64(name string) (int64, error) { // URLParamInt64Default returns the url query parameter as int64 value from a request, // if not found or parse failed then "def" is returned. -func (ctx *context) URLParamInt64Default(name string, def int64) int64 { +func (ctx *Context) URLParamInt64Default(name string, def int64) int64 { v, err := ctx.URLParamInt64(name) if err != nil { return def @@ -2450,7 +1292,7 @@ func (ctx *context) URLParamInt64Default(name string, def int64) int64 { // URLParamFloat64 returns the url query parameter as float64 value from a request, // returns an error and -1 if parse failed. -func (ctx *context) URLParamFloat64(name string) (float64, error) { +func (ctx *Context) URLParamFloat64(name string) (float64, error) { if v := ctx.URLParam(name); v != "" { n, err := strconv.ParseFloat(v, 64) if err != nil { @@ -2464,7 +1306,7 @@ func (ctx *context) URLParamFloat64(name string) (float64, error) { // URLParamFloat64Default returns the url query parameter as float64 value from a request, // if not found or parse failed then "def" is returned. -func (ctx *context) URLParamFloat64Default(name string, def float64) float64 { +func (ctx *Context) URLParamFloat64Default(name string, def float64) float64 { v, err := ctx.URLParamFloat64(name) if err != nil { return def @@ -2475,13 +1317,13 @@ func (ctx *context) URLParamFloat64Default(name string, def float64) float64 { // URLParamBool returns the url query parameter as boolean value from a request, // returns an error if parse failed. -func (ctx *context) URLParamBool(name string) (bool, error) { +func (ctx *Context) URLParamBool(name string) (bool, error) { return strconv.ParseBool(ctx.URLParam(name)) } // URLParams returns a map of GET query parameters separated by comma if more than one // it returns an empty map if nothing found. -func (ctx *context) URLParams() map[string]string { +func (ctx *Context) URLParams() map[string]string { q := ctx.request.URL.Query() values := make(map[string]string, len(q)) @@ -2493,7 +1335,7 @@ func (ctx *context) URLParams() map[string]string { } // No need anymore, net/http checks for the Form already. -// func (ctx *context) askParseForm() error { +// func (ctx *Context) askParseForm() error { // if ctx.request.Form == nil { // if err := ctx.request.ParseForm(); err != nil { // return err @@ -2506,7 +1348,7 @@ func (ctx *context) URLParams() map[string]string { // including both the URL field's query parameters and the POST or PUT form data. // // Returns the "def" if not found. -func (ctx *context) FormValueDefault(name string, def string) string { +func (ctx *Context) FormValueDefault(name string, def string) string { if form, has := ctx.form(); has { if v := form[name]; len(v) > 0 { return v[0] @@ -2527,7 +1369,7 @@ func FormValueDefault(r *http.Request, name string, def string, postMaxMemory in // FormValue returns a single parsed form value by its "name", // including both the URL field's query parameters and the POST or PUT form data. -func (ctx *context) FormValue(name string) string { +func (ctx *Context) FormValue(name string) string { return ctx.FormValueDefault(name, "") } @@ -2537,14 +1379,14 @@ func (ctx *context) FormValue(name string) string { // The default form's memory maximum size is 32MB, it can be changed by the // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument. // NOTE: A check for nil is necessary. -func (ctx *context) FormValues() map[string][]string { +func (ctx *Context) FormValues() map[string][]string { form, _ := ctx.form() return form } // Form contains the parsed form data, including both the URL // field's query parameters and the POST or PUT form data. -func (ctx *context) form() (form map[string][]string, found bool) { +func (ctx *Context) form() (form map[string][]string, found bool) { return GetForm(ctx.request, ctx.app.ConfigurationReadOnly().GetPostMaxMemory(), ctx.app.ConfigurationReadOnly().GetDisableBodyConsumptionOnUnmarshal()) } @@ -2621,7 +1463,7 @@ func GetForm(r *http.Request, postMaxMemory int64, resetBody bool) (form map[str // or PUT body parameters based on a "name". // // If not found then "def" is returned instead. -func (ctx *context) PostValueDefault(name string, def string) string { +func (ctx *Context) PostValueDefault(name string, def string) string { ctx.form() if v := ctx.request.PostForm[name]; len(v) > 0 { return v[0] @@ -2631,13 +1473,13 @@ func (ctx *context) PostValueDefault(name string, def string) string { // PostValue returns the parsed form data from POST, PATCH, // or PUT body parameters based on a "name" -func (ctx *context) PostValue(name string) string { +func (ctx *Context) PostValue(name string) string { return ctx.PostValueDefault(name, "") } // PostValueTrim returns the parsed form data from POST, PATCH, // or PUT body parameters based on a "name", without trailing spaces. -func (ctx *context) PostValueTrim(name string) string { +func (ctx *Context) PostValueTrim(name string) string { return strings.TrimSpace(ctx.PostValue(name)) } @@ -2645,7 +1487,7 @@ func (ctx *context) PostValueTrim(name string) string { // or PUT body parameters based on a "name", as int. // // If not found returns -1 and a non-nil error. -func (ctx *context) PostValueInt(name string) (int, error) { +func (ctx *Context) PostValueInt(name string) (int, error) { v := ctx.PostValue(name) if v == "" { return -1, ErrNotFound @@ -2657,7 +1499,7 @@ func (ctx *context) PostValueInt(name string) (int, error) { // or PUT body parameters based on a "name", as int. // // If not found or parse errors returns the "def". -func (ctx *context) PostValueIntDefault(name string, def int) int { +func (ctx *Context) PostValueIntDefault(name string, def int) int { if v, err := ctx.PostValueInt(name); err == nil { return v } @@ -2669,7 +1511,7 @@ func (ctx *context) PostValueIntDefault(name string, def int) int { // or PUT body parameters based on a "name", as float64. // // If not found returns -1 and a non-nil error. -func (ctx *context) PostValueInt64(name string) (int64, error) { +func (ctx *Context) PostValueInt64(name string) (int64, error) { v := ctx.PostValue(name) if v == "" { return -1, ErrNotFound @@ -2681,7 +1523,7 @@ func (ctx *context) PostValueInt64(name string) (int64, error) { // or PUT body parameters based on a "name", as int64. // // If not found or parse errors returns the "def". -func (ctx *context) PostValueInt64Default(name string, def int64) int64 { +func (ctx *Context) PostValueInt64Default(name string, def int64) int64 { if v, err := ctx.PostValueInt64(name); err == nil { return v } @@ -2693,7 +1535,7 @@ func (ctx *context) PostValueInt64Default(name string, def int64) int64 { // or PUT body parameters based on a "name", as float64. // // If not found returns -1 and a non-nil error. -func (ctx *context) PostValueFloat64(name string) (float64, error) { +func (ctx *Context) PostValueFloat64(name string) (float64, error) { v := ctx.PostValue(name) if v == "" { return -1, ErrNotFound @@ -2705,7 +1547,7 @@ func (ctx *context) PostValueFloat64(name string) (float64, error) { // or PUT body parameters based on a "name", as float64. // // If not found or parse errors returns the "def". -func (ctx *context) PostValueFloat64Default(name string, def float64) float64 { +func (ctx *Context) PostValueFloat64Default(name string, def float64) float64 { if v, err := ctx.PostValueFloat64(name); err == nil { return v } @@ -2717,7 +1559,7 @@ func (ctx *context) PostValueFloat64Default(name string, def float64) float64 { // or PUT body parameters based on a "name", as bool. // // If not found or value is false, then it returns false, otherwise true. -func (ctx *context) PostValueBool(name string) (bool, error) { +func (ctx *Context) PostValueBool(name string) (bool, error) { v := ctx.PostValue(name) if v == "" { return false, ErrNotFound @@ -2731,7 +1573,7 @@ func (ctx *context) PostValueBool(name string) (bool, error) { // // The default form's memory maximum size is 32MB, it can be changed by the // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument. -func (ctx *context) PostValues(name string) []string { +func (ctx *Context) PostValues(name string) []string { ctx.form() return ctx.request.PostForm[name] } @@ -2743,7 +1585,7 @@ func (ctx *context) PostValues(name string) []string { // `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument. // // Example: https://github.com/kataras/iris/tree/master/_examples/file-server/upload-file -func (ctx *context) FormFile(key string) (multipart.File, *multipart.FileHeader, error) { +func (ctx *Context) FormFile(key string) (multipart.File, *multipart.FileHeader, error) { // we don't have access to see if the request is body stream // and then the ParseMultipartForm can be useless // here but do it in order to apply the post limit, @@ -2781,7 +1623,7 @@ func (ctx *context) FormFile(key string) (multipart.File, *multipart.FileHeader, // See `FormFile` to a more controlled to receive a file. // // Example: https://github.com/kataras/iris/tree/master/_examples/file-server/upload-files -func (ctx *context) UploadFormFiles(destDirectory string, before ...func(Context, *multipart.FileHeader)) (n int64, err error) { +func (ctx *Context) UploadFormFiles(destDirectory string, before ...func(*Context, *multipart.FileHeader)) (n int64, err error) { err = ctx.request.ParseMultipartForm(ctx.app.ConfigurationReadOnly().GetPostMaxMemory()) if err != nil { return 0, err @@ -2828,7 +1670,7 @@ func uploadTo(fh *multipart.FileHeader, destDirectory string) (int64, error) { } // AbsoluteURI parses the "s" and returns its absolute URI form. -func (ctx *context) AbsoluteURI(s string) string { +func (ctx *Context) AbsoluteURI(s string) string { if s == "" { return "" } @@ -2889,7 +1731,7 @@ func (ctx *context) AbsoluteURI(s string) string { // you can set it to 301 (Permant redirect) // or 303 (StatusSeeOther) if POST method, // or StatusTemporaryRedirect(307) if that's nessecery. -func (ctx *context) Redirect(urlToRedirect string, statusHeader ...int) { +func (ctx *Context) Redirect(urlToRedirect string, statusHeader ...int) { ctx.StopExecution() // get the previous status code given by the end-developer. status := ctx.GetStatusCode() @@ -2918,7 +1760,7 @@ func (ctx *context) Redirect(urlToRedirect string, statusHeader ...int) { // SetMaxRequestBodySize sets a limit to the request body size // should be called before reading the request body from the client. -func (ctx *context) SetMaxRequestBodySize(limitOverBytes int64) { +func (ctx *Context) SetMaxRequestBodySize(limitOverBytes int64) { ctx.request.Body = http.MaxBytesReader(ctx.writer, ctx.request.Body, limitOverBytes) } @@ -2927,7 +1769,7 @@ func (ctx *context) SetMaxRequestBodySize(limitOverBytes int64) { // but you can change that behavior by passing the `WithoutBodyConsumptionOnUnmarshal` iris option. // // However, whenever you can use the `ctx.Request().Body` instead. -func (ctx *context) GetBody() ([]byte, error) { +func (ctx *Context) GetBody() ([]byte, error) { return GetBody(ctx.request, ctx.app.ConfigurationReadOnly().GetDisableBodyConsumptionOnUnmarshal()) } @@ -2960,11 +1802,7 @@ type Validator interface { // Examples of usage: context.ReadJSON, context.ReadXML. // // Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-custom-via-unmarshaler/main.go -// -// UnmarshalBody does not check about gzipped data. -// Do not rely on compressed data incoming to your server. The main reason is: https://en.wikipedia.org/wiki/Zip_bomb -// However you are still free to read the `ctx.Request().Body io.Reader` manually. -func (ctx *context) UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) error { +func (ctx *Context) UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) error { if ctx.request.Body == nil { return fmt.Errorf("unmarshal: empty body: %w", ErrNotFound) } @@ -2998,14 +1836,14 @@ func (ctx *context) UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) e return ctx.app.Validate(outPtr) } -func (ctx *context) shouldOptimize() bool { +func (ctx *Context) shouldOptimize() bool { return ctx.app.ConfigurationReadOnly().GetEnableOptimizations() } // ReadJSON reads JSON from request's body and binds it to a value of any json-valid type. // // Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-json/main.go -func (ctx *context) ReadJSON(outPtr interface{}) error { +func (ctx *Context) ReadJSON(outPtr interface{}) error { unmarshaler := json.Unmarshal if ctx.shouldOptimize() { unmarshaler = jsoniter.Unmarshal @@ -3016,14 +1854,14 @@ func (ctx *context) ReadJSON(outPtr interface{}) error { // ReadXML reads XML from request's body and binds it to a value of any xml-valid type. // // Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-xml/main.go -func (ctx *context) ReadXML(outPtr interface{}) error { +func (ctx *Context) ReadXML(outPtr interface{}) error { return ctx.UnmarshalBody(outPtr, UnmarshalerFunc(xml.Unmarshal)) } // ReadYAML reads YAML from request's body and binds it to the "outPtr" value. // // Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-yaml/main.go -func (ctx *context) ReadYAML(outPtr interface{}) error { +func (ctx *Context) ReadYAML(outPtr interface{}) error { return ctx.UnmarshalBody(outPtr, UnmarshalerFunc(yaml.Unmarshal)) } @@ -3050,7 +1888,7 @@ var ErrEmptyForm = errors.New("empty form") // in order to ignore that error use the `err != nil && !iris.IsErrPath(err)`. // // Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-form/main.go -func (ctx *context) ReadForm(formObject interface{}) error { +func (ctx *Context) ReadForm(formObject interface{}) error { values := ctx.FormValues() if len(values) == 0 { if ctx.app.ConfigurationReadOnly().GetFireEmptyFormError() { @@ -3072,7 +1910,7 @@ func (ctx *context) ReadForm(formObject interface{}) error { // in order to ignore that error use the `err != nil && !iris.IsErrPath(err)`. // // Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-query/main.go -func (ctx *context) ReadQuery(ptr interface{}) error { +func (ctx *Context) ReadQuery(ptr interface{}) error { values := ctx.request.URL.Query() if len(values) == 0 { return nil @@ -3088,7 +1926,7 @@ func (ctx *context) ReadQuery(ptr interface{}) error { // ReadProtobuf binds the body to the "ptr" of a proto Message and returns any error. // Look `ReadJSONProtobuf` too. -func (ctx *context) ReadProtobuf(ptr proto.Message) error { +func (ctx *Context) ReadProtobuf(ptr proto.Message) error { rawData, err := ctx.GetBody() if err != nil { return err @@ -3104,7 +1942,7 @@ var defaultProtobufUnmarshalOptions ProtoUnmarshalOptions // ReadJSONProtobuf reads a JSON body request into the given "ptr" proto.Message. // Look `ReadProtobuf` too. -func (ctx *context) ReadJSONProtobuf(ptr proto.Message, opts ...ProtoUnmarshalOptions) error { +func (ctx *Context) ReadJSONProtobuf(ptr proto.Message, opts ...ProtoUnmarshalOptions) error { rawData, err := ctx.GetBody() if err != nil { return err @@ -3119,7 +1957,7 @@ func (ctx *context) ReadJSONProtobuf(ptr proto.Message, opts ...ProtoUnmarshalOp } // ReadMsgPack binds the request body of msgpack format to the "ptr" and returns any error. -func (ctx *context) ReadMsgPack(ptr interface{}) error { +func (ctx *Context) ReadMsgPack(ptr interface{}) error { rawData, err := ctx.GetBody() if err != nil { return err @@ -3137,7 +1975,7 @@ func (ctx *context) ReadMsgPack(ptr interface{}) error { // If a GET method request then it reads from a form (or URL Query), otherwise // it tries to match (depending on the request content-type) the data format e.g. // JSON, Protobuf, MsgPack, XML, YAML, MultipartForm and binds the result to the "ptr". -func (ctx *context) ReadBody(ptr interface{}) error { +func (ctx *Context) ReadBody(ptr interface{}) error { if ctx.Method() == http.MethodGet { return ctx.ReadForm(ptr) } @@ -3194,22 +2032,22 @@ func (ctx *context) ReadBody(ptr interface{}) error { // writing the response. However, such behavior may not be supported // by all HTTP/2 clients. Handlers should read before writing if // possible to maximize compatibility. -func (ctx *context) Write(rawBody []byte) (int, error) { +func (ctx *Context) Write(rawBody []byte) (int, error) { return ctx.writer.Write(rawBody) } // Writef formats according to a format specifier and writes to the response. // // Returns the number of bytes written and any write error encountered. -func (ctx *context) Writef(format string, a ...interface{}) (n int, err error) { - return ctx.writer.Writef(format, a...) +func (ctx *Context) Writef(format string, a ...interface{}) (n int, err error) { + return fmt.Fprintf(ctx.writer, format, a...) } // WriteString writes a simple string to the response. // // Returns the number of bytes written and any write error encountered. -func (ctx *context) WriteString(body string) (n int, err error) { - return ctx.writer.WriteString(body) +func (ctx *Context) WriteString(body string) (n int, err error) { + return io.WriteString(ctx.writer, body) } const ( @@ -3233,8 +2071,6 @@ const ( ContentEncodingHeaderKey = "Content-Encoding" // GzipHeaderValue is the header value of "gzip". GzipHeaderValue = "gzip" - // FlateHeaderValue is the header value of "deflate". - FlateHeaderValue = "deflate" // AcceptEncodingHeaderKey is the header key of "Accept-Encoding". AcceptEncodingHeaderKey = "Accept-Encoding" // VaryHeaderKey is the header key of "Vary". @@ -3255,7 +2091,7 @@ func IsZeroTime(t time.Time) bool { // time.RFC850, and time.ANSIC. // // Look `context#FormatTime` for the opossite operation (Time to string). -var ParseTime = func(ctx Context, text string) (t time.Time, err error) { +var ParseTime = func(ctx *Context, text string) (t time.Time, err error) { t, err = time.Parse(ctx.Application().ConfigurationReadOnly().GetTimeFormat(), text) if err != nil { return http.ParseTime(text) @@ -3269,7 +2105,7 @@ var ParseTime = func(ctx Context, text string) (t time.Time, err error) { // which defines the format. // // Look `context#ParseTime` for the opossite operation (string to Time). -var FormatTime = func(ctx Context, t time.Time) string { +var FormatTime = func(ctx *Context, t time.Time) string { return t.Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat()) } @@ -3277,7 +2113,7 @@ var FormatTime = func(ctx Context, t time.Time) string { // If "modtime" is zero then it does nothing. // // It's mostly internally on core/router and context packages. -func (ctx *context) SetLastModified(modtime time.Time) { +func (ctx *Context) SetLastModified(modtime time.Time) { if !IsZeroTime(modtime) { ctx.Header(LastModifiedHeaderKey, FormatTime(ctx, modtime.UTC())) // or modtime.UTC()? } @@ -3310,7 +2146,7 @@ var ErrPreconditionFailed = errors.New("precondition failed") // or if parsing time from the header failed. See `ErrPreconditionFailed` too. // // It's mostly used internally, e.g. `context#WriteWithExpiration`. -func (ctx *context) CheckIfModifiedSince(modtime time.Time) (bool, error) { +func (ctx *Context) CheckIfModifiedSince(modtime time.Time) (bool, error) { if method := ctx.Method(); method != http.MethodGet && method != http.MethodHead { return false, fmt.Errorf("method: %w", ErrPreconditionFailed) } @@ -3335,7 +2171,7 @@ func (ctx *context) CheckIfModifiedSince(modtime time.Time) (bool, error) { // and any "ETag" are removed before the response sent. // // It's mostly used internally on core/router/fs.go and context methods. -func (ctx *context) WriteNotModified() { +func (ctx *Context) WriteNotModified() { // RFC 7232 section 4.1: // a sender SHOULD NOT generate representation metadata other than the // above listed fields unless said metadata exists for the purpose of @@ -3353,7 +2189,7 @@ func (ctx *context) WriteNotModified() { // WriteWithExpiration works like `Write` but it will check if a resource is modified, // based on the "modtime" input argument, // otherwise sends a 304 status code in order to let the client-side render the cached content. -func (ctx *context) WriteWithExpiration(body []byte, modtime time.Time) (int, error) { +func (ctx *Context) WriteWithExpiration(body []byte, modtime time.Time) (int, error) { if modified, err := ctx.CheckIfModifiedSince(modtime); !modified && err == nil { ctx.WriteNotModified() return 0, nil @@ -3374,7 +2210,7 @@ func (ctx *context) WriteWithExpiration(body []byte, modtime time.Time) (int, er // * if response body is streamed from slow external sources. // * if response body must be streamed to the client in chunks. // (aka `http server push`). -func (ctx *context) StreamWriter(writer func(w io.Writer) error) error { +func (ctx *Context) StreamWriter(writer func(w io.Writer) error) error { cancelCtx := ctx.Request().Context() notifyClosed := cancelCtx.Done() @@ -3396,12 +2232,22 @@ func (ctx *context) StreamWriter(writer func(w io.Writer) error) error { // | Body Writers with compression | // +------------------------------------------------------------+ -// ClientSupportsGzip retruns true if the client supports gzip compression. -func (ctx *context) ClientSupportsGzip() bool { +// ClientSupportsEncoding reports whether the +// client expects one of the given "encodings" compression. +// +// Note, instead of `Compress` method, this one just reports back the first valid encoding it sees, +// meaning that request accept-encoding offers don't matter here. +func (ctx *Context) ClientSupportsEncoding(encodings ...string) bool { + if len(encodings) == 0 { + return false + } + if h := ctx.GetHeader(AcceptEncodingHeaderKey); h != "" { for _, v := range strings.Split(h, ",") { - if strings.Contains(v, GzipHeaderValue) { // we do Contains because sometimes browsers has the q=, we don't use it atm. || strings.Contains(v,"deflate"){ - return true + for _, encoding := range encodings { + if strings.Contains(v, encoding) { + return true + } } } } @@ -3409,120 +2255,86 @@ func (ctx *context) ClientSupportsGzip() bool { return false } -// ErrGzipNotSupported may be returned from `WriteGzip` and `GzipReader` methods if -// the client does not support the "gzip" compression. -var ErrGzipNotSupported = errors.New("client does not support gzip compression") - -// WriteGzip accepts bytes, which are compressed to gzip format and sent to the client. -// returns the number of bytes written and an error ( if the client doesn't support gzip compression) -// -// You may re-use this function in the same handler -// to write more data many times without any troubles. -func (ctx *context) WriteGzip(b []byte) (int, error) { - if !ctx.ClientSupportsGzip() { - return 0, ErrGzipNotSupported - } - - return ctx.GzipResponseWriter().Write(b) -} - -// TryWriteGzip accepts bytes, which are compressed to gzip format and sent to the client. -// If client does not supprots gzip then the contents are written as they are, uncompressed. -func (ctx *context) TryWriteGzip(b []byte) (int, error) { - n, err := ctx.WriteGzip(b) - if err != nil { - // check if the error came from gzip not allowed and not the writer itself - if err == ErrGzipNotSupported { - // client didn't supported gzip, write them uncompressed: - return ctx.writer.Write(b) +// Compress enables or disables the compress response writer. +// if the client expects a valid compression algorithm then this +// will change the response writer to a compress writer instead. +// All future write and rich write methods will respect this option. +// Usage: +// app.Use(func(ctx iris.Context){ +// err := ctx.Compress(true) +// ctx.Next() +// }) +// The recommendation is to compress data as much as possible and therefore to use this field, +// but some types of resources, such as jpeg images, are already compressed. +// Sometimes, using additional compression doesn't reduce payload size and +// can even make the payload longer. +func (ctx *Context) Compress(enable bool) error { + cw, ok := ctx.writer.(*CompressResponseWriter) + if enable { + if ok { + // already a compress writer. + return nil } - } - return n, err -} -// GzipResponseWriter converts the current response writer into a response writer -// which when its .Write called it compress the data to gzip and writes them to the client. -// -// Can be also disabled with its .Disable and .ResetBody to rollback to the usual response writer. -func (ctx *context) GzipResponseWriter() *GzipResponseWriter { - // if it's already a gzip response writer then just return it - if gzipResWriter, ok := ctx.writer.(*GzipResponseWriter); ok { - return gzipResWriter - } - // if it's not acquire a new from a pool - // and register that as the ctx.ResponseWriter. - gzipResWriter := AcquireGzipResponseWriter() - gzipResWriter.BeginGzipResponse(ctx.writer) - ctx.ResetResponseWriter(gzipResWriter) - return gzipResWriter -} - -// Gzip enables or disables (if enabled before) the gzip response writer,if the client -// supports gzip compression, so the following response data will -// be sent as compressed gzip data to the client. -func (ctx *context) Gzip(enable bool) { - if enable { - if ctx.ClientSupportsGzip() { - _ = ctx.GzipResponseWriter() + w, err := AcquireCompressResponseWriter(ctx.writer, ctx.request, -1) + if err != nil { + return err } + ctx.writer = w } else { - if gzipResWriter, ok := ctx.writer.(*GzipResponseWriter); ok { - gzipResWriter.Disable() + if ok { + cw.Disabled = true } } -} -type gzipReadCloser struct { - requestReader io.ReadCloser - gzipReader io.ReadCloser -} - -func (rc *gzipReadCloser) Close() error { - rc.gzipReader.Close() - return rc.requestReader.Close() -} - -func (rc *gzipReadCloser) Read(p []byte) (n int, err error) { - return rc.gzipReader.Read(p) + return nil } -const gzipEncodingHeaderValue = "gzip" - -// GzipReader accepts a boolean, which, if set to true -// it wraps the request body reader with a gzip reader one (decompress request data on read).. +// CompressReader accepts a boolean, which, if set to true +// it wraps the request body reader with a reader which decompresses request data before read. // If the "enable" input argument is false then the request body will reset to the default one. // -// Useful when incoming request data are gzip compressed. +// Useful when incoming request data are compressed. // All future calls of `ctx.GetBody/ReadXXX/UnmarshalBody` methods will respect this option. // // Usage: // app.Use(func(ctx iris.Context){ -// ctx.GzipReader(true) +// err := ctx.CompressReader(true) // ctx.Next() // }) +// More: +// if cr, ok := ctx.Request().Body.(*CompressReader); ok { +// cr.Src // the original request body +// cr.Encoding // the compression algorithm. +// } // -// If a client request's body is not gzip compressed then -// it returns with a `ErrGzipNotSupported` error, which can be safety ignored. -// -// See `GzipReader` package-level middleware too. -func (ctx *context) GzipReader(enable bool) error { +// It returns `ErrRequestNotCompressed` if client's request data are not compressed +// (or empty) +// or `ErrNotSupportedCompression` if server missing the decompression algorithm. +func (ctx *Context) CompressReader(enable bool) error { + cr, ok := ctx.request.Body.(*CompressReader) if enable { - if ctx.GetHeader(ContentEncodingHeaderKey) == gzipEncodingHeaderValue { - reader, err := gzip.NewReader(ctx.request.Body) - if err != nil { - return err - } - - // Wrap the reader so on Close it will close both request body and gzip reader. - ctx.request.Body = &gzipReadCloser{requestReader: ctx.request.Body, gzipReader: reader} + if ok { + // already called. return nil } - return ErrGzipNotSupported - } + encoding := ctx.GetHeader(ContentEncodingHeaderKey) + if encoding == IDENTITY { + // no transformation whatsoever, return nil error and + // don't wrap the body reader. + return nil + } - if gzipReader, ok := ctx.request.Body.(*gzipReadCloser); ok { - ctx.request.Body = gzipReader.requestReader + r, err := NewCompressReader(ctx.request.Body, encoding) + if err != nil { + return err + } + ctx.request.Body = r + } else { + if ok { + ctx.request.Body = cr.Src + } } return nil @@ -3548,7 +2360,7 @@ const ( // Look .ViewData and .View too. // // Example: https://github.com/kataras/iris/tree/master/_examples/view/context-view-data/ -func (ctx *context) ViewLayout(layoutTmplFile string) { +func (ctx *Context) ViewLayout(layoutTmplFile string) { ctx.values.Set(ctx.app.ConfigurationReadOnly().GetViewLayoutContextKey(), layoutTmplFile) } @@ -3570,7 +2382,7 @@ func (ctx *context) ViewLayout(layoutTmplFile string) { // Look .ViewLayout and .View too. // // Example: https://github.com/kataras/iris/tree/master/_examples/view/context-view-data/ -func (ctx *context) ViewData(key string, value interface{}) { +func (ctx *Context) ViewData(key string, value interface{}) { viewDataContextKey := ctx.app.ConfigurationReadOnly().GetViewDataContextKey() if key == "" { ctx.values.Set(viewDataContextKey, value) @@ -3599,7 +2411,7 @@ func (ctx *context) ViewData(key string, value interface{}) { // // Similarly to `viewData := ctx.Values().Get("iris.viewData")` or // `viewData := ctx.Values().Get(ctx.Application().ConfigurationReadOnly().GetViewDataContextKey())`. -func (ctx *context) GetViewData() map[string]interface{} { +func (ctx *Context) GetViewData() map[string]interface{} { viewDataContextKey := ctx.app.ConfigurationReadOnly().GetViewDataContextKey() v := ctx.values.Get(viewDataContextKey) @@ -3640,7 +2452,7 @@ func (ctx *context) GetViewData() map[string]interface{} { // Look .ViewData and .ViewLayout too. // // Examples: https://github.com/kataras/iris/tree/master/_examples/view -func (ctx *context) View(filename string, optionalViewModel ...interface{}) error { +func (ctx *Context) View(filename string, optionalViewModel ...interface{}) error { ctx.ContentType(ContentHTMLHeaderValue) cfg := ctx.app.ConfigurationReadOnly() @@ -3704,19 +2516,19 @@ const ( ) // Binary writes out the raw bytes as binary data. -func (ctx *context) Binary(data []byte) (int, error) { +func (ctx *Context) Binary(data []byte) (int, error) { ctx.ContentType(ContentBinaryHeaderValue) return ctx.Write(data) } // Text writes out a string as plain text. -func (ctx *context) Text(format string, args ...interface{}) (int, error) { +func (ctx *Context) Text(format string, args ...interface{}) (int, error) { ctx.ContentType(ContentTextHeaderValue) return ctx.Writef(format, args...) } // HTML writes out a string as text/html. -func (ctx *context) HTML(format string, args ...interface{}) (int, error) { +func (ctx *Context) HTML(format string, args ...interface{}) (int, error) { ctx.ContentType(ContentHTMLHeaderValue) return ctx.Writef(format, args...) } @@ -3777,7 +2589,7 @@ var ( ) // WriteJSON marshals the given interface object and writes the JSON response to the 'writer'. -// Ignores StatusCode, Gzip, StreamingJSON options. +// Ignores StatusCode, Compress, StreamingJSON options. func WriteJSON(writer io.Writer, v interface{}, options JSON, optimize bool) (int, error) { var ( result []byte @@ -3868,7 +2680,7 @@ var DefaultJSONOptions = JSON{} // JSON marshals the given interface object and writes the JSON response to the client. // If the value is a compatible `proto.Message` one // then it only uses the options.Proto settings to marshal. -func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) { +func (ctx *Context) JSON(v interface{}, opts ...JSON) (n int, err error) { options := DefaultJSONOptions if len(opts) > 0 { @@ -3894,7 +2706,7 @@ func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) { if err != nil { ctx.app.Logger().Debugf("JSON: %v", err) - ctx.StatusCode(http.StatusInternalServerError) // it handles the fallback to normal mode here which also removes the gzip headers. + ctx.StatusCode(http.StatusInternalServerError) // it handles the fallback to normal mode here which also removes any compression headers. return 0, err } return ctx.writer.Written(), err @@ -3957,7 +2769,7 @@ func WriteJSONP(writer io.Writer, v interface{}, options JSONP, optimize bool) ( var DefaultJSONPOptions = JSONP{} // JSONP marshals the given interface object and writes the JSON response to the client. -func (ctx *context) JSONP(v interface{}, opts ...JSONP) (int, error) { +func (ctx *Context) JSONP(v interface{}, opts ...JSONP) (int, error) { options := DefaultJSONPOptions if len(opts) > 0 { @@ -4054,7 +2866,7 @@ var DefaultXMLOptions = XML{} // XML marshals the given interface object and writes the XML response to the client. // To render maps as XML see the `XMLMap` package-level function. -func (ctx *context) XML(v interface{}, opts ...XML) (int, error) { +func (ctx *Context) XML(v interface{}, opts ...XML) (int, error) { options := DefaultXMLOptions if len(opts) > 0 { @@ -4084,7 +2896,7 @@ func (ctx *context) XML(v interface{}, opts ...XML) (int, error) { // send a response of content type "application/problem+xml" instead. // // Read more at: https://github.com/kataras/iris/wiki/Routing-error-handlers -func (ctx *context) Problem(v interface{}, opts ...ProblemOptions) (int, error) { +func (ctx *Context) Problem(v interface{}, opts ...ProblemOptions) (int, error) { options := DefaultProblemOptions if len(opts) > 0 { options = opts[0] @@ -4134,7 +2946,7 @@ func WriteMarkdown(writer io.Writer, markdownB []byte, options Markdown) (int, e var DefaultMarkdownOptions = Markdown{} // Markdown parses the markdown to html and renders its result to the client. -func (ctx *context) Markdown(markdownB []byte, opts ...Markdown) (int, error) { +func (ctx *Context) Markdown(markdownB []byte, opts ...Markdown) (int, error) { options := DefaultMarkdownOptions if len(opts) > 0 { @@ -4154,7 +2966,7 @@ func (ctx *context) Markdown(markdownB []byte, opts ...Markdown) (int, error) { } // YAML marshals the "v" using the yaml marshaler and renders its result to the client. -func (ctx *context) YAML(v interface{}) (int, error) { +func (ctx *Context) YAML(v interface{}) (int, error) { out, err := yaml.Marshal(v) if err != nil { ctx.app.Logger().Debugf("YAML: %v", err) @@ -4167,7 +2979,7 @@ func (ctx *context) YAML(v interface{}) (int, error) { } // Protobuf parses the "v" of proto Message and renders its result to the client. -func (ctx *context) Protobuf(v proto.Message) (int, error) { +func (ctx *Context) Protobuf(v proto.Message) (int, error) { out, err := proto.Marshal(v) if err != nil { return 0, err @@ -4178,7 +2990,7 @@ func (ctx *context) Protobuf(v proto.Message) (int, error) { } // MsgPack parses the "v" of msgpack format and renders its result to the client. -func (ctx *context) MsgPack(v interface{}) (int, error) { +func (ctx *Context) MsgPack(v interface{}) (int, error) { out, err := msgpack.Marshal(v) if err != nil { return 0, err @@ -4220,7 +3032,7 @@ type ContentNegotiator interface { // content types, although it can accept any custom mime type with []byte. // Content type is already set. // Use it with caution, 99.9% you don't need this but it's here for extreme cases. - Negotiate(ctx Context) (int, error) + Negotiate(ctx *Context) (int, error) } // N is a struct which can be passed on the `Context.Negotiate` method. @@ -4283,7 +3095,7 @@ const negotiationContextKey = "iris.negotiation_builder" // for specific content type(s), charset(s) and encoding algorithm(s). // // See `Negotiate` method too. -func (ctx *context) Negotiation() *NegotiationBuilder { +func (ctx *Context) Negotiation() *NegotiationBuilder { if n := ctx.values.Get(negotiationContextKey); n != nil { return n.(*NegotiationBuilder) } @@ -4343,7 +3155,7 @@ func parseHeader(headerValue string) []string { // Supports the above without quality values. // // Read more at: https://github.com/kataras/iris/wiki/Content-negotiation -func (ctx *context) Negotiate(v interface{}) (int, error) { +func (ctx *Context) Negotiate(v interface{}) (int, error) { contentType, charset, encoding, content := ctx.Negotiation().Build() if v == nil { v = content @@ -4360,8 +3172,8 @@ func (ctx *context) Negotiate(v interface{}) (int, error) { charset = ctx.app.ConfigurationReadOnly().GetCharset() } - if encoding == "gzip" { - ctx.Gzip(true) + if encoding != "" { + ctx.Compress(true) } ctx.contentTypeOnce(contentType, charset) @@ -4635,12 +3447,9 @@ func (n *NegotiationBuilder) Charset(charset ...string) *NegotiationBuilder { return n } -// Encoding registers one or more encoding algorithms by name, i.e gzip, deflate. +// Encoding registers one or more encoding algorithms by name, i.e gzip, deflate, br, snappy, s2. // that a client should match for (through Accept-Encoding header). // -// Only the "gzip" can be handlded automatically as it's the only builtin encoding algorithm -// to serve resources. -// // Returns itself for recursive calls. func (n *NegotiationBuilder) Encoding(encoding ...string) *NegotiationBuilder { n.encoding = append(n.encoding, encoding...) @@ -4688,55 +3497,6 @@ func (n *NegotiationBuilder) Clear() *NegotiationBuilder { return n } -func negotiationMatch(in []string, priorities []string) string { - // e.g. - // match json: - // in: text/html, application/json - // prioritities: application/json - // not match: - // in: text/html, application/json - // prioritities: text/xml - // match html: - // in: text/html, application/json - // prioritities: */* - // not match: - // in: application/json - // prioritities: text/xml - // match json: - // in: text/html, application/* - // prioritities: application/json - - if len(priorities) == 0 { - return "" - } - - if len(in) == 0 { - return priorities[0] - } - - for _, accepted := range in { - for _, p := range priorities { - // wildcard is */* or text/* and etc. - // so loop through each char. - for i, n := 0, len(accepted); i < n; i++ { - if accepted[i] != p[i] { - break - } - - if accepted[i] == '*' || p[i] == '*' { - return p - } - - if i == n-1 { - return p - } - } - } - } - - return "" -} - // NegotiationAcceptBuilder builds the accepted mime types and charset // // and "Accept-Charset" headers respectfully. @@ -4916,8 +3676,8 @@ func (n *NegotiationAcceptBuilder) EncodingGzip() *NegotiationAcceptBuilder { // ServeContent uses it to handle requests using If-Match, If-None-Match, or If-Range. // // Note that *os.File implements the io.ReadSeeker interface. -// Note that gzip compression can be registered through `ctx.Gzip(true)` or `app.Use(iris.Gzip)`. -func (ctx *context) ServeContent(content io.ReadSeeker, filename string, modtime time.Time) { +// Note that compression can be registered through `ctx.Compress(true)` or `app.Use(iris.Compress)`. +func (ctx *Context) ServeContent(content io.ReadSeeker, filename string, modtime time.Time) { ctx.ServeContentWithRate(content, filename, modtime, 0, 0) } @@ -4941,7 +3701,7 @@ func (rs *rateReadSeeker) Read(buf []byte) (int, error) { // ServeContentWithRate same as `ServeContent` but it can throttle the speed of reading // and though writing the "content" to the client. -func (ctx *context) ServeContentWithRate(content io.ReadSeeker, filename string, modtime time.Time, limit float64, burst int) { +func (ctx *Context) ServeContentWithRate(content io.ReadSeeker, filename string, modtime time.Time, limit float64, burst int) { if limit > 0 { content = &rateReadSeeker{ ReadSeeker: content, @@ -4967,14 +3727,14 @@ func (ctx *context) ServeContentWithRate(content io.ReadSeeker, filename string, // // Use it when you want to serve assets like css and javascript files. // If client should confirm and save the file use the `SendFile` instead. -// Note that gzip compression can be registered through `ctx.Gzip(true)` or `app.Use(iris.Gzip)`. -func (ctx *context) ServeFile(filename string) error { +// Note that compression can be registered through `ctx.Compress(true)` or `app.Use(iris.Compress)`. +func (ctx *Context) ServeFile(filename string) error { return ctx.ServeFileWithRate(filename, 0, 0) } // ServeFileWithRate same as `ServeFile` but it can throttle the speed of reading // and though writing the file to the client. -func (ctx *context) ServeFileWithRate(filename string, limit float64, burst int) error { +func (ctx *Context) ServeFileWithRate(filename string, limit float64, burst int) error { f, err := os.Open(filename) if err != nil { ctx.StatusCode(http.StatusNotFound) @@ -5006,15 +3766,15 @@ func (ctx *context) ServeFileWithRate(filename string, limit float64, burst int) } // SendFile sends a file as an attachment, that is downloaded and saved locally from client. -// Note that gzip compression can be registered through `ctx.Gzip(true)` or `app.Use(iris.Gzip)`. +// Note that compression can be registered through `ctx.Compress(true)` or `app.Use(iris.Compress)`. // Use `ServeFile` if a file should be served as a page asset instead. -func (ctx *context) SendFile(src string, destName string) error { +func (ctx *Context) SendFile(src string, destName string) error { return ctx.SendFileWithRate(src, destName, 0, 0) } // SendFileWithRate same as `SendFile` but it can throttle the speed of reading // and though writing the file to the client. -func (ctx *context) SendFileWithRate(src, destName string, limit float64, burst int) error { +func (ctx *Context) SendFileWithRate(src, destName string, limit float64, burst int) error { if destName == "" { destName = filepath.Base(src) } @@ -5039,7 +3799,7 @@ const ( // as their (last) variadic input argument to amend the to-be-sent cookie. // // The "op" is the operation code, 0 is GET, 1 is SET and 2 is REMOVE. -type CookieOption func(ctx Context, c *http.Cookie, op uint8) +type CookieOption func(ctx *Context, c *http.Cookie, op uint8) // CookieIncluded reports whether the "cookie.Name" is in the list of "cookieNames". // Notes: @@ -5073,7 +3833,7 @@ func sanitizeCookieName(n string) string { // If set it will add the cookie to (on `CookieSet`, `CookieSetKV`, `CookieUpsert`) // or remove the cookie from (on `CookieRemove`) the Request object too. func CookieAllowReclaim(cookieNames ...string) CookieOption { - return func(ctx Context, c *http.Cookie, op uint8) { + return func(ctx *Context, c *http.Cookie, op uint8) { if op == OpCookieGet { return } @@ -5120,7 +3880,7 @@ func CookieAllowReclaim(cookieNames ...string) CookieOption { // It sets the cookie's Domain field (if was empty) and // it also sets the cookie's SameSite to lax mode too. func CookieAllowSubdomains(cookieNames ...string) CookieOption { - return func(ctx Context, c *http.Cookie, _ uint8) { + return func(ctx *Context, c *http.Cookie, _ uint8) { if c.Domain != "" { return // already set. } @@ -5142,7 +3902,7 @@ func CookieAllowSubdomains(cookieNames ...string) CookieOption { // // See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details. func CookieSameSite(sameSite http.SameSite) CookieOption { - return func(_ Context, c *http.Cookie, op uint8) { + return func(_ *Context, c *http.Cookie, op uint8) { if op == OpCookieSet { c.SameSite = sameSite } @@ -5151,7 +3911,7 @@ func CookieSameSite(sameSite http.SameSite) CookieOption { // CookieSecure sets the cookie's Secure option if the current request's // connection is using TLS. See `CookieHTTPOnly` too. -func CookieSecure(ctx Context, c *http.Cookie, op uint8) { +func CookieSecure(ctx *Context, c *http.Cookie, op uint8) { if op == OpCookieSet { if ctx.Request().TLS != nil { c.Secure = true @@ -5164,7 +3924,7 @@ func CookieSecure(ctx Context, c *http.Cookie, op uint8) { // HttpOnly field defaults to true for `RemoveCookie` and `SetCookieKV`. // See `CookieSecure` too. func CookieHTTPOnly(httpOnly bool) CookieOption { - return func(_ Context, c *http.Cookie, op uint8) { + return func(_ *Context, c *http.Cookie, op uint8) { if op == OpCookieSet { c.HttpOnly = httpOnly } @@ -5174,7 +3934,7 @@ func CookieHTTPOnly(httpOnly bool) CookieOption { // CookiePath is a `CookieOption`. // Use it to change the cookie's Path field. func CookiePath(path string) CookieOption { - return func(_ Context, c *http.Cookie, op uint8) { + return func(_ *Context, c *http.Cookie, op uint8) { if op > OpCookieGet { // on set and remove. c.Path = path } @@ -5183,7 +3943,7 @@ func CookiePath(path string) CookieOption { // CookieCleanPath is a `CookieOption`. // Use it to clear the cookie's Path field, exactly the same as `CookiePath("")`. -func CookieCleanPath(_ Context, c *http.Cookie, op uint8) { +func CookieCleanPath(_ *Context, c *http.Cookie, op uint8) { if op > OpCookieGet { c.Path = "" } @@ -5192,7 +3952,7 @@ func CookieCleanPath(_ Context, c *http.Cookie, op uint8) { // CookieExpires is a `CookieOption`. // Use it to change the cookie's Expires and MaxAge fields by passing the lifetime of the cookie. func CookieExpires(durFromNow time.Duration) CookieOption { - return func(_ Context, c *http.Cookie, op uint8) { + return func(_ *Context, c *http.Cookie, op uint8) { if op == OpCookieSet { c.Expires = time.Now().Add(durFromNow) c.MaxAge = int(durFromNow.Seconds()) @@ -5239,7 +3999,7 @@ type SecureCookie interface { // // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/securecookie func CookieEncoding(encoding SecureCookie, cookieNames ...string) CookieOption { - return func(_ Context, c *http.Cookie, op uint8) { + return func(_ *Context, c *http.Cookie, op uint8) { if op == OpCookieDel { return } @@ -5286,7 +4046,7 @@ const cookieOptionsContextKey = "iris.cookie.options" // * CookieEncoding // // Example at: https://github.com/kataras/iris/tree/master/_examples/cookies/securecookie -func (ctx *context) AddCookieOptions(options ...CookieOption) { +func (ctx *Context) AddCookieOptions(options ...CookieOption) { if len(options) == 0 { return } @@ -5300,7 +4060,7 @@ func (ctx *context) AddCookieOptions(options ...CookieOption) { ctx.values.Set(cookieOptionsContextKey, options) } -func (ctx *context) applyCookieOptions(c *http.Cookie, op uint8, override []CookieOption) { +func (ctx *Context) applyCookieOptions(c *http.Cookie, op uint8, override []CookieOption) { if v := ctx.values.Get(cookieOptionsContextKey); v != nil { if options, ok := v.([]CookieOption); ok { for _, opt := range options { @@ -5318,7 +4078,7 @@ func (ctx *context) applyCookieOptions(c *http.Cookie, op uint8, override []Cook // ClearCookieOptions clears any previously registered cookie options. // See `AddCookieOptions` too. -func (ctx *context) ClearCookieOptions() { +func (ctx *Context) ClearCookieOptions() { ctx.values.Remove(cookieOptionsContextKey) } @@ -5326,7 +4086,7 @@ func (ctx *context) ClearCookieOptions() { // Use of the "options" is not required, they can be used to amend the "cookie". // // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic -func (ctx *context) SetCookie(cookie *http.Cookie, options ...CookieOption) { +func (ctx *Context) SetCookie(cookie *http.Cookie, options ...CookieOption) { ctx.applyCookieOptions(cookie, OpCookieSet, options) http.SetCookie(ctx.writer, cookie) } @@ -5337,7 +4097,7 @@ const setCookieHeaderKey = "Set-Cookie" // but it will also perform a replacement of the cookie // if already set by a previous `SetCookie` call. // It reports whether the cookie is new (true) or an existing one was updated (false). -func (ctx *context) UpsertCookie(cookie *http.Cookie, options ...CookieOption) bool { +func (ctx *Context) UpsertCookie(cookie *http.Cookie, options ...CookieOption) bool { ctx.applyCookieOptions(cookie, OpCookieSet, options) header := ctx.ResponseWriter().Header() @@ -5383,7 +4143,7 @@ var SetCookieKVExpiration = time.Duration(8760) * time.Hour // iris.CookieHTTPOnly(false) // // Examples: https://github.com/kataras/iris/tree/master/_examples/cookies/basic -func (ctx *context) SetCookieKV(name, value string, options ...CookieOption) { +func (ctx *Context) SetCookieKV(name, value string, options ...CookieOption) { c := &http.Cookie{} c.Path = "/" c.Name = name @@ -5406,7 +4166,7 @@ func (ctx *context) SetCookieKV(name, value string, options ...CookieOption) { // cookie, err := ctx.Request().Cookie("name") // // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic -func (ctx *context) GetCookie(name string, options ...CookieOption) string { +func (ctx *Context) GetCookie(name string, options ...CookieOption) string { c, err := ctx.request.Cookie(name) if err != nil { return "" @@ -5430,7 +4190,7 @@ var ( // Tip: change the cookie's path to the current one by: RemoveCookie("name", iris.CookieCleanPath) // // Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic -func (ctx *context) RemoveCookie(name string, options ...CookieOption) { +func (ctx *Context) RemoveCookie(name string, options ...CookieOption) { c := &http.Cookie{} c.Name = name c.Value = "" @@ -5447,7 +4207,7 @@ func (ctx *context) RemoveCookie(name string, options ...CookieOption) { // VisitAllCookies takes a visitor function which is called // on each (request's) cookies' name and value. -func (ctx *context) VisitAllCookies(visitor func(name string, value string)) { +func (ctx *Context) VisitAllCookies(visitor func(name string, value string)) { for _, cookie := range ctx.request.Cookies() { visitor(cookie.Name, cookie.Value) } @@ -5458,7 +4218,7 @@ var maxAgeExp = regexp.MustCompile(`maxage=(\d+)`) // MaxAge returns the "cache-control" request header's value // seconds as int64 // if header not found or parse failed then it returns -1. -func (ctx *context) MaxAge() int64 { +func (ctx *Context) MaxAge() int64 { header := ctx.GetHeader(CacheControlHeaderKey) if header == "" { return -1 @@ -5479,7 +4239,7 @@ func (ctx *context) MaxAge() int64 { // Record transforms the context's basic and direct responseWriter to a *ResponseRecorder // which can be used to reset the body, reset headers, get the body, // get & set the status code at any time and more. -func (ctx *context) Record() { +func (ctx *Context) Record() { if w, ok := ctx.writer.(*responseWriter); ok { recorder := AcquireResponseRecorder() recorder.BeginRecord(w) @@ -5489,7 +4249,7 @@ func (ctx *context) Record() { // Recorder returns the context's ResponseRecorder // if not recording then it starts recording and returns the new context's ResponseRecorder -func (ctx *context) Recorder() *ResponseRecorder { +func (ctx *Context) Recorder() *ResponseRecorder { ctx.Record() return ctx.writer.(*ResponseRecorder) } @@ -5497,7 +4257,7 @@ func (ctx *context) Recorder() *ResponseRecorder { // IsRecording returns the response recorder and a true value // when the response writer is recording the status code, body, headers and so on, // else returns nil and false. -func (ctx *context) IsRecording() (*ResponseRecorder, bool) { +func (ctx *Context) IsRecording() (*ResponseRecorder, bool) { // NOTE: // two return values in order to minimize the if statement: // if (Recording) then writer = Recorder() @@ -5528,7 +4288,7 @@ var ErrTransactionInterrupt = errors.New("transaction interrupted") // Transactions have their own middleware ecosystem also. // // See https://github.com/kataras/iris/tree/master/_examples/ for more -func (ctx *context) BeginTransaction(pipe func(t *Transaction)) { +func (ctx *Context) BeginTransaction(pipe func(t *Transaction)) { // do NOT begin a transaction when the previous transaction has been failed // and it was requested scoped or SkipTransactions called manually. if ctx.TransactionsSkipped() { @@ -5564,12 +4324,12 @@ const skipTransactionsContextKey = "iris.transactions.skip" // SkipTransactions if called then skip the rest of the transactions // or all of them if called before the first transaction -func (ctx *context) SkipTransactions() { +func (ctx *Context) SkipTransactions() { ctx.values.Set(skipTransactionsContextKey, 1) } // TransactionsSkipped returns true if the transactions skipped or canceled at all. -func (ctx *context) TransactionsSkipped() bool { +func (ctx *Context) TransactionsSkipped() bool { if n, err := ctx.values.GetInt(skipTransactionsContextKey); err == nil && n == 1 { return true } @@ -5599,7 +4359,7 @@ func (ctx *context) TransactionsSkipped() bool { // context's Values and the Session are kept in order to be able to communicate via the result route. // // It's for extreme use cases, 99% of the times will never be useful for you. -func (ctx *context) Exec(method string, path string) { +func (ctx *Context) Exec(method string, path string) { if path == "" { return } @@ -5643,20 +4403,20 @@ func (ctx *context) Exec(method string, path string) { // RouteExists reports whether a particular route exists // It will search from the current subdomain of context's host, if not inside the root domain. -func (ctx *context) RouteExists(method, path string) bool { +func (ctx *Context) RouteExists(method, path string) bool { return ctx.app.RouteExists(ctx, method, path) } const ( - reflectValueContextKey = "_iris_context_reflect_value" + reflectValueContextKey = "iris.context.reflect_value" // ControllerContextKey returns the context key from which // the `Context.Controller` method returns the store's value. - ControllerContextKey = "_iris_controller_reflect_value" + ControllerContextKey = "iris.controller.reflect_value" ) // ReflectValue caches and returns a []reflect.Value{reflect.ValueOf(ctx)}. // It's just a helper to maintain variable inside the context itself. -func (ctx *context) ReflectValue() []reflect.Value { +func (ctx *Context) ReflectValue() []reflect.Value { if v := ctx.values.Get(reflectValueContextKey); v != nil { return v.([]reflect.Value) } @@ -5670,7 +4430,7 @@ var emptyValue reflect.Value // Controller returns a reflect Value of the custom Controller from which this handler executed. // It will return a Kind() == reflect.Invalid if the handler was not executed from within a controller. -func (ctx *context) Controller() reflect.Value { +func (ctx *Context) Controller() reflect.Value { if v := ctx.values.Get(ControllerContextKey); v != nil { return v.(reflect.Value) } @@ -5695,7 +4455,7 @@ type DependenciesMap map[reflect.Type]reflect.Value // in sake of minimum performance cost. // // See `UnregisterDependency` too. -func (ctx *context) RegisterDependency(v interface{}) { +func (ctx *Context) RegisterDependency(v interface{}) { if v == nil { return } @@ -5723,7 +4483,7 @@ func (ctx *context) RegisterDependency(v interface{}) { // UnregisterDependency removes a dependency based on its type. // Reports whether a dependency with that type was found and removed successfully. -func (ctx *context) UnregisterDependency(typ reflect.Type) bool { +func (ctx *Context) UnregisterDependency(typ reflect.Type) bool { cv := ctx.Values().Get(DependenciesContextKey) if cv != nil { m, ok := cv.(DependenciesMap) @@ -5741,7 +4501,7 @@ func (ctx *context) UnregisterDependency(typ reflect.Type) bool { // of the Application, which contains methods that are safe // to be executed at serve-time. The full app's fields // and methods are not available here for the developer's safety. -func (ctx *context) Application() Application { +func (ctx *Context) Application() Application { return ctx.app } @@ -5752,14 +4512,14 @@ const idContextKey = "iris.context.id" // so it can be rendered on `Context.String` method. // // See `GetID` and `middleware/requestid` too. -func (ctx *context) SetID(id interface{}) { +func (ctx *Context) SetID(id interface{}) { ctx.values.Set(idContextKey, id) } // GetID returns the Request Context's ID. // It returns nil if not given by a prior `SetID` call. // See `middleware/requestid` too. -func (ctx *context) GetID() interface{} { +func (ctx *Context) GetID() interface{} { return ctx.values.Get(idContextKey) } @@ -5767,6 +4527,6 @@ func (ctx *context) GetID() interface{} { // // It returns the Context's ID given by a `SetID`call, // followed by the client's IP and the method:uri. -func (ctx *context) String() string { +func (ctx *Context) String() string { return fmt.Sprintf("[%s] %s ▶ %s:%s", ctx.GetID(), ctx.RemoteAddr(), ctx.Method(), ctx.Request().RequestURI) } diff --git a/context/gzip_response_writer.go b/context/gzip_response_writer.go deleted file mode 100644 index ccca889ecd..0000000000 --- a/context/gzip_response_writer.go +++ /dev/null @@ -1,231 +0,0 @@ -package context - -import ( - "fmt" - "io" - "sync" - - "github.com/klauspost/compress/gzip" -) - -// compressionPool is a wrapper of sync.Pool, to initialize a new compression writer pool -type compressionPool struct { - sync.Pool - Level int -} - -// +------------------------------------------------------------+ -// |GZIP raw io.writer, our gzip response writer will use that. | -// +------------------------------------------------------------+ - -// default writer pool with Compressor's level set to -1 -var gzipPool = &compressionPool{Level: -1} - -// acquireGzipWriter prepares a gzip writer and returns it. -// -// see releaseGzipWriter too. -func acquireGzipWriter(w io.Writer) *gzip.Writer { - v := gzipPool.Get() - if v == nil { - gzipWriter, err := gzip.NewWriterLevel(w, gzipPool.Level) - if err != nil { - return nil - } - return gzipWriter - } - gzipWriter := v.(*gzip.Writer) - gzipWriter.Reset(w) - return gzipWriter -} - -// releaseGzipWriter called when flush/close and put the gzip writer back to the pool. -// -// see acquireGzipWriter too. -func releaseGzipWriter(gzipWriter *gzip.Writer) { - gzipWriter.Close() - gzipPool.Put(gzipWriter) -} - -// writeGzip writes a compressed form of p to the underlying io.Writer. The -// compressed bytes are not necessarily flushed until the Writer is closed. -func writeGzip(w io.Writer, b []byte) (int, error) { - gzipWriter := acquireGzipWriter(w) - n, err := gzipWriter.Write(b) - if err != nil { - releaseGzipWriter(gzipWriter) - return -1, err - } - err = gzipWriter.Flush() - releaseGzipWriter(gzipWriter) - return n, err -} - -var gzpool = sync.Pool{New: func() interface{} { return &GzipResponseWriter{} }} - -// AcquireGzipResponseWriter returns a new *GzipResponseWriter from the pool. -// Releasing is done automatically when request and response is done. -func AcquireGzipResponseWriter() *GzipResponseWriter { - w := gzpool.Get().(*GzipResponseWriter) - return w -} - -func releaseGzipResponseWriter(w *GzipResponseWriter) { - gzpool.Put(w) -} - -// GzipResponseWriter is an upgraded response writer which writes compressed data to the underline ResponseWriter. -// -// It's a separate response writer because iris gives you the ability to "fallback" and "roll-back" the gzip encoding if something -// went wrong with the response, and write http errors in plain form instead. -type GzipResponseWriter struct { - ResponseWriter - chunks []byte - disabled bool -} - -var _ ResponseWriter = (*GzipResponseWriter)(nil) - -// BeginGzipResponse accepts a ResponseWriter -// and prepares the new gzip response writer. -// It's being called per-handler, when caller decide -// to change the response writer type. -func (w *GzipResponseWriter) BeginGzipResponse(underline ResponseWriter) { - w.ResponseWriter = underline - - w.chunks = w.chunks[0:0] - w.disabled = false -} - -// EndResponse called right before the contents of this -// response writer are flushed to the client. -func (w *GzipResponseWriter) EndResponse() { - releaseGzipResponseWriter(w) - w.ResponseWriter.EndResponse() -} - -// Write prepares the data write to the gzip writer and finally to its -// underline response writer, returns the uncompressed len(contents). -func (w *GzipResponseWriter) Write(contents []byte) (int, error) { - // save the contents to serve them (only gzip data here) - w.chunks = append(w.chunks, contents...) - return len(contents), nil -} - -// Writef formats according to a format specifier and writes to the response. -// -// Returns the number of bytes written and any write error encountered. -func (w *GzipResponseWriter) Writef(format string, a ...interface{}) (n int, err error) { - n, err = fmt.Fprintf(w, format, a...) - if err == nil { - h := w.ResponseWriter.Header() - if h[ContentTypeHeaderKey] == nil { - h.Set(ContentTypeHeaderKey, ContentTextHeaderValue) - } - } - - return -} - -// WriteString prepares the string data write to the gzip writer and finally to its -// underline response writer, returns the uncompressed len(contents). -func (w *GzipResponseWriter) WriteString(s string) (n int, err error) { - n, err = w.Write([]byte(s)) - if err == nil { - h := w.ResponseWriter.Header() - if h[ContentTypeHeaderKey] == nil { - h.Set(ContentTypeHeaderKey, ContentTextHeaderValue) - } - } - return -} - -// WriteNow compresses and writes that data to the underline response writer, -// returns the compressed written len. -// -// Use `WriteNow` instead of `Write` -// when you need to know the compressed written size before -// the `FlushResponse`, note that you can't post any new headers -// after that, so that information is not closed to the handler anymore. -func (w *GzipResponseWriter) WriteNow(contents []byte) (int, error) { - if w.disabled { - // type noOp struct{} - // - // func (n noOp) Write([]byte) (int, error) { - // return 0, nil - // } - // - // var noop = noOp{} - // problem solved with w.gzipWriter.Reset(noop): - // - // the below Write called multiple times but not from here, - // the gzip writer does something to the writer, even if we don't call the - // w.gzipWriter.Write it does call the underline http.ResponseWriter - // multiple times, and therefore it changes the content-length - // the problem that results to the #723. - // - // Or a better idea, acquire and adapt the gzip writer on-time when is not disabled. - // So that is not needed any more: - // w.gzipWriter.Reset(noop) - - return w.ResponseWriter.Write(contents) - } - - AddGzipHeaders(w.ResponseWriter) - // if not `WriteNow` but "Content-Length" header - // is exists, then delete it before `.Write` - // Content-Length should not be there. - // no, for now at least: w.ResponseWriter.Header().Del(contentLengthHeaderKey) - return writeGzip(w.ResponseWriter, contents) -} - -// AddGzipHeaders just adds the headers "Vary" to "Accept-Encoding" -// and "Content-Encoding" to "gzip". -func AddGzipHeaders(w ResponseWriter) { - w.Header().Add(VaryHeaderKey, AcceptEncodingHeaderKey) - w.Header().Add(ContentEncodingHeaderKey, GzipHeaderValue) -} - -// Body returns the body tracked from the writer so far, -// do not use this for edit. -func (w *GzipResponseWriter) Body() []byte { - return w.chunks -} - -// ResetBody resets the response body. -// Implements the `ResponseWriterBodyReseter`. -func (w *GzipResponseWriter) ResetBody() { - w.chunks = w.chunks[0:0] -} - -// Disable turns off the gzip compression for the next .Write's data, -// if called then the contents are being written in plain form. -func (w *GzipResponseWriter) Disable() { - w.disabled = true -} - -// Reset disables the gzip content writer, clears headers, sets the status code to 200 -// and clears the cached body. -// -// Implements the `ResponseWriterReseter`. -func (w *GzipResponseWriter) Reset() bool { - // disable gzip content writer. - w.Disable() - // clear headers. - h := w.ResponseWriter.Header() - for k := range h { - h[k] = nil - } - // restore status code. - w.WriteHeader(defaultStatusCode) - // reset body. - w.ResetBody() - - return true -} - -// FlushResponse validates the response headers in order to be compatible with the gzip written data -// and writes the data to the underline ResponseWriter. -func (w *GzipResponseWriter) FlushResponse() { - _, _ = w.WriteNow(w.chunks) - w.ResponseWriter.FlushResponse() -} diff --git a/context/handler.go b/context/handler.go index fc7133cb19..0dc2347034 100644 --- a/context/handler.go +++ b/context/handler.go @@ -82,7 +82,7 @@ func (expr *nameExpr) MatchString(s string) bool { // // If Handler panics, the server (the caller of Handler) assumes that the effect of the panic was isolated to the active request. // It recovers the panic, logs a stack trace to the server error log, and hangs up the connection. -type Handler func(Context) +type Handler func(*Context) // Handlers is just a type of slice of []Handler. // @@ -235,7 +235,7 @@ func ingoreMainHandlerName(name string) bool { // based on the incoming request. // // See `NewConditionalHandler` for more. -type Filter func(Context) bool +type Filter func(*Context) bool // NewConditionalHandler returns a single Handler which can be registered // as a middleware. @@ -254,7 +254,7 @@ type Filter func(Context) bool // // Example can be found at: _examples/routing/conditional-chain. func NewConditionalHandler(filter Filter, handlers ...Handler) Handler { - return func(ctx Context) { + return func(ctx *Context) { if filter(ctx) { // Note that we don't want just to fire the incoming handlers, we must make sure // that it won't break any further handler chain diff --git a/context/i18n.go b/context/i18n.go index 407ef0bee9..e856c5e9b1 100644 --- a/context/i18n.go +++ b/context/i18n.go @@ -6,7 +6,7 @@ import "golang.org/x/text/language" // Read the "i18n" package fo details. type I18nReadOnly interface { Tags() []language.Tag - GetLocale(ctx Context) Locale + GetLocale(ctx *Context) Locale Tr(lang string, format string, args ...interface{}) string } diff --git a/context/pool.go b/context/pool.go index 49f4738b93..04905df39d 100644 --- a/context/pool.go +++ b/context/pool.go @@ -6,41 +6,26 @@ import ( ) // Pool is the context pool, it's used inside router and the framework by itself. -// -// It's the only one real implementation inside this package because it used widely. type Pool struct { - pool *sync.Pool - newFunc func() Context // we need a field otherwise is not working if we change the return value + pool *sync.Pool } // New creates and returns a new context pool. -func New(newFunc func() Context) *Pool { - c := &Pool{pool: &sync.Pool{}, newFunc: newFunc} - c.pool.New = func() interface{} { return c.newFunc() } - return c -} - -// Attach changes the pool's return value Context. -// -// The new Context should explicitly define the `Next()` -// and `Do(context.Handlers)` functions. -// -// Example: https://github.com/kataras/iris/blob/master/_examples/routing/custom-context/method-overriding/main.go -func (c *Pool) Attach(newFunc func() Context) { - c.newFunc = newFunc +func New(newFunc func() interface{}) *Pool { + return &Pool{pool: &sync.Pool{New: newFunc}} } // Acquire returns a Context from pool. // See Release. -func (c *Pool) Acquire(w http.ResponseWriter, r *http.Request) Context { - ctx := c.pool.Get().(Context) +func (c *Pool) Acquire(w http.ResponseWriter, r *http.Request) *Context { + ctx := c.pool.Get().(*Context) ctx.BeginRequest(w, r) return ctx } // Release puts a Context back to its pull, this function releases its resources. // See Acquire. -func (c *Pool) Release(ctx Context) { +func (c *Pool) Release(ctx *Context) { ctx.EndRequest() c.pool.Put(ctx) } @@ -48,6 +33,6 @@ func (c *Pool) Release(ctx Context) { // ReleaseLight will just release the object back to the pool, but the // clean method is caller's responsibility now, currently this is only used // on `SPABuilder`. -func (c *Pool) ReleaseLight(ctx Context) { +func (c *Pool) ReleaseLight(ctx *Context) { c.pool.Put(ctx) } diff --git a/context/problem.go b/context/problem.go index 1008412384..27031b78ed 100644 --- a/context/problem.go +++ b/context/problem.go @@ -73,7 +73,7 @@ func (p Problem) getURI(key string) string { } // Updates "type" field to absolute URI, recursively. -func (p Problem) updateURIsToAbs(ctx Context) { +func (p Problem) updateURIsToAbs(ctx *Context) { if p == nil { return } @@ -271,7 +271,7 @@ type ProblemOptions struct { // Should return time.Time, time.Duration, int64, int, float64 or string. // // Overrides the RetryAfter field. - RetryAfterFunc func(Context) interface{} + RetryAfterFunc func(*Context) interface{} } func parseDurationToSeconds(dur time.Duration) int64 { @@ -310,7 +310,7 @@ func (o *ProblemOptions) parseRetryAfter(value interface{}, timeLayout string) s } // Apply accepts a Context and applies specific response-time options. -func (o *ProblemOptions) Apply(ctx Context) { +func (o *ProblemOptions) Apply(ctx *Context) { retryAfterHeaderValue := "" timeLayout := ctx.Application().ConfigurationReadOnly().GetTimeFormat() diff --git a/context/request_params.go b/context/request_params.go index f92e7fb9a4..3f7efd713b 100644 --- a/context/request_params.go +++ b/context/request_params.go @@ -95,7 +95,7 @@ func (r RequestParams) GetIntUnslashed(key string) (int, bool) { // The value is a function which accepts the parameter index // and it should return the value as the parameter type evaluator expects it. // i.e [reflect.TypeOf("string")] = func(paramIndex int) interface{} { -// return func(ctx Context) { +// return func(ctx *Context) { // return ctx.Params().GetEntryAt(paramIndex).ValueRaw.() // } // } @@ -107,7 +107,7 @@ func (r RequestParams) GetIntUnslashed(key string) (int, bool) { // when on the second requested path, the 'pssecond' should be empty. var ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ reflect.TypeOf(""): func(paramIndex int) interface{} { - return func(ctx Context) string { + return func(ctx *Context) string { if ctx.Params().Len() <= paramIndex { return "" } @@ -115,7 +115,7 @@ var ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ } }, reflect.TypeOf(int(1)): func(paramIndex int) interface{} { - return func(ctx Context) int { + return func(ctx *Context) int { if ctx.Params().Len() <= paramIndex { return 0 } @@ -125,7 +125,7 @@ var ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ } }, reflect.TypeOf(int8(1)): func(paramIndex int) interface{} { - return func(ctx Context) int8 { + return func(ctx *Context) int8 { if ctx.Params().Len() <= paramIndex { return 0 } @@ -133,7 +133,7 @@ var ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ } }, reflect.TypeOf(int16(1)): func(paramIndex int) interface{} { - return func(ctx Context) int16 { + return func(ctx *Context) int16 { if ctx.Params().Len() <= paramIndex { return 0 } @@ -141,7 +141,7 @@ var ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ } }, reflect.TypeOf(int32(1)): func(paramIndex int) interface{} { - return func(ctx Context) int32 { + return func(ctx *Context) int32 { if ctx.Params().Len() <= paramIndex { return 0 } @@ -149,7 +149,7 @@ var ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ } }, reflect.TypeOf(int64(1)): func(paramIndex int) interface{} { - return func(ctx Context) int64 { + return func(ctx *Context) int64 { if ctx.Params().Len() <= paramIndex { return 0 } @@ -157,7 +157,7 @@ var ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ } }, reflect.TypeOf(uint(1)): func(paramIndex int) interface{} { - return func(ctx Context) uint { + return func(ctx *Context) uint { if ctx.Params().Len() <= paramIndex { return 0 } @@ -165,7 +165,7 @@ var ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ } }, reflect.TypeOf(uint8(1)): func(paramIndex int) interface{} { - return func(ctx Context) uint8 { + return func(ctx *Context) uint8 { if ctx.Params().Len() <= paramIndex { return 0 } @@ -173,7 +173,7 @@ var ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ } }, reflect.TypeOf(uint16(1)): func(paramIndex int) interface{} { - return func(ctx Context) uint16 { + return func(ctx *Context) uint16 { if ctx.Params().Len() <= paramIndex { return 0 } @@ -181,7 +181,7 @@ var ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ } }, reflect.TypeOf(uint32(1)): func(paramIndex int) interface{} { - return func(ctx Context) uint32 { + return func(ctx *Context) uint32 { if ctx.Params().Len() <= paramIndex { return 0 } @@ -189,7 +189,7 @@ var ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ } }, reflect.TypeOf(uint64(1)): func(paramIndex int) interface{} { - return func(ctx Context) uint64 { + return func(ctx *Context) uint64 { if ctx.Params().Len() <= paramIndex { return 0 } @@ -197,7 +197,7 @@ var ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ } }, reflect.TypeOf(true): func(paramIndex int) interface{} { - return func(ctx Context) bool { + return func(ctx *Context) bool { if ctx.Params().Len() <= paramIndex { return false } @@ -219,7 +219,7 @@ func ParamResolverByTypeAndIndex(typ reflect.Type, paramIndex int) (reflect.Valu /* NO: // This could work but its result is not exact type, so direct binding is not possible. resolver := m.ParamResolver - fn := func(ctx context.Context) interface{} { + fn := func(ctx *context.Context) interface{} { entry, _ := ctx.Params().GetEntry(paramName) return resolver(entry) } @@ -227,10 +227,10 @@ func ParamResolverByTypeAndIndex(typ reflect.Type, paramIndex int) (reflect.Valu // This works but it is slower on serve-time. paramNameValue := []reflect.Value{reflect.ValueOf(paramName)} - var fnSignature func(context.Context) string + var fnSignature func(*context.Context) string return reflect.MakeFunc(reflect.ValueOf(&fnSignature).Elem().Type(), func(in []reflect.Value) []reflect.Value { return in[0].MethodByName("Params").Call(emptyIn)[0].MethodByName("Get").Call(paramNameValue) - // return []reflect.Value{reflect.ValueOf(in[0].Interface().(context.Context).Params().Get(paramName))} + // return []reflect.Value{reflect.ValueOf(in[0].Interface().(*context.Context).Params().Get(paramName))} }) // */ diff --git a/context/response_recorder.go b/context/response_recorder.go index 676ae58be0..0b406d9147 100644 --- a/context/response_recorder.go +++ b/context/response_recorder.go @@ -1,13 +1,12 @@ package context import ( - "fmt" "net/http" "sync" ) // Recorder the middleware to enable response writer recording ( ResponseWriter -> ResponseRecorder) -var Recorder = func(ctx Context) { +var Recorder = func(ctx *Context) { ctx.Record() ctx.Next() } @@ -90,20 +89,6 @@ func (w *ResponseRecorder) Write(contents []byte) (int, error) { return len(contents), nil } -// Writef formats according to a format specifier and writes to the response. -// -// Returns the number of bytes written and any write error encountered. -func (w *ResponseRecorder) Writef(format string, a ...interface{}) (n int, err error) { - return fmt.Fprintf(w, format, a...) -} - -// WriteString writes a simple string to the response. -// -// Returns the number of bytes written and any write error encountered -func (w *ResponseRecorder) WriteString(s string) (n int, err error) { - return w.Write([]byte(s)) -} - // SetBody overrides the body and sets it to a slice of bytes value. func (w *ResponseRecorder) SetBody(b []byte) { w.chunks = b diff --git a/context/response_writer.go b/context/response_writer.go index 6ed21f4d9a..573ccd1029 100644 --- a/context/response_writer.go +++ b/context/response_writer.go @@ -3,8 +3,6 @@ package context import ( "bufio" "errors" - "fmt" - "io" "net" "net/http" "sync" @@ -15,8 +13,8 @@ import ( // // Note: Only this ResponseWriter is an interface in order to be able // for developers to change the response writer of the Context via `context.ResetResponseWriter`. -// The rest of the response writers implementations (ResponseRecorder & GzipResponseWriter) are coupled to the internal -// ResponseWriter implementation(*responseWriter). +// The rest of the response writers implementations (ResponseRecorder & CompressResponseWriter) +// are coupled to the internal ResponseWriter implementation(*responseWriter). // // A ResponseWriter may not be used after the Handler // has returned. @@ -45,16 +43,6 @@ type ResponseWriter interface { // IsHijacked reports whether this response writer's connection is hijacked. IsHijacked() bool - // Writef formats according to a format specifier and writes to the response. - // - // Returns the number of bytes written and any write error encountered. - Writef(format string, a ...interface{}) (n int, err error) - - // WriteString writes a simple string to the response. - // - // Returns the number of bytes written and any write error encountered. - WriteString(s string) (n int, err error) - // StatusCode returns the status code header value. StatusCode() int @@ -279,23 +267,6 @@ func (w *responseWriter) Write(contents []byte) (int, error) { return n, err } -// Writef formats according to a format specifier and writes to the response. -// -// Returns the number of bytes written and any write error encountered. -func (w *responseWriter) Writef(format string, a ...interface{}) (n int, err error) { - return fmt.Fprintf(w, format, a...) -} - -// WriteString writes a simple string to the response. -// -// Returns the number of bytes written and any write error encountered. -func (w *responseWriter) WriteString(s string) (int, error) { - w.tryWriteHeader() - n, err := io.WriteString(w.ResponseWriter, s) - w.written += n - return n, err -} - // StatusCode returns the status code header value func (w *responseWriter) StatusCode() int { return w.statusCode diff --git a/context/transaction.go b/context/transaction.go index 778146e7bb..823b1502d5 100644 --- a/context/transaction.go +++ b/context/transaction.go @@ -34,14 +34,14 @@ func NewTransactionErrResult() TransactionErrResult { type TransactionScope interface { // EndTransaction returns if can continue to the next transactions or not (false) // called after Complete, empty or not empty error - EndTransaction(maybeErr TransactionErrResult, ctx Context) bool + EndTransaction(maybeErr TransactionErrResult, ctx *Context) bool } // TransactionScopeFunc the transaction's scope signature -type TransactionScopeFunc func(maybeErr TransactionErrResult, ctx Context) bool +type TransactionScopeFunc func(maybeErr TransactionErrResult, ctx *Context) bool // EndTransaction ends the transaction with a callback to itself, implements the TransactionScope interface -func (tsf TransactionScopeFunc) EndTransaction(maybeErr TransactionErrResult, ctx Context) bool { +func (tsf TransactionScopeFunc) EndTransaction(maybeErr TransactionErrResult, ctx *Context) bool { return tsf(maybeErr, ctx) } @@ -60,13 +60,13 @@ func (tsf TransactionScopeFunc) EndTransaction(maybeErr TransactionErrResult, ct // // For more information please visit the tests. type Transaction struct { - context Context - parent Context + context *Context + parent *Context hasError bool scope TransactionScope } -func newTransaction(from *context) *Transaction { +func newTransaction(from *Context) *Transaction { tempCtx := *from writer := tempCtx.ResponseWriter().Clone() tempCtx.ResetResponseWriter(writer) @@ -80,7 +80,7 @@ func newTransaction(from *context) *Transaction { } // Context returns the current context of the transaction. -func (t *Transaction) Context() Context { +func (t *Transaction) Context() *Context { return t.context } @@ -138,7 +138,7 @@ func (t *Transaction) Complete(err error) { // independent 'silent' scope, if transaction fails (if transaction.IsFailure() == true) // then its response is not written to the real context no error is provided to the user. // useful for the most cases. -var TransientTransactionScope = TransactionScopeFunc(func(maybeErr TransactionErrResult, ctx Context) bool { +var TransientTransactionScope = TransactionScopeFunc(func(maybeErr TransactionErrResult, ctx *Context) bool { if maybeErr.IsFailure() { ctx.Recorder().Reset() // this response is skipped because it's empty. } @@ -150,7 +150,7 @@ var TransientTransactionScope = TransactionScopeFunc(func(maybeErr TransactionEr // if scope fails (if transaction.IsFailure() == true) // then the rest of the context's response (transaction or normal flow) // is not written to the client, and an error status code is written instead. -var RequestTransactionScope = TransactionScopeFunc(func(maybeErr TransactionErrResult, ctx Context) bool { +var RequestTransactionScope = TransactionScopeFunc(func(maybeErr TransactionErrResult, ctx *Context) bool { if maybeErr.IsFailure() { // we need to register a beforeResponseFlush event here in order diff --git a/core/handlerconv/from_std.go b/core/handlerconv/from_std.go index bc8aff2b30..d42ac1c000 100644 --- a/core/handlerconv/from_std.go +++ b/core/handlerconv/from_std.go @@ -27,7 +27,7 @@ func FromStd(handler interface{}) context.Handler { // // handlerFunc.ServeHTTP(w,r) // - return func(ctx context.Context) { + return func(ctx *context.Context) { h.ServeHTTP(ctx.ResponseWriter(), ctx.Request()) } } @@ -51,7 +51,7 @@ func FromStd(handler interface{}) context.Handler { // No valid handler passed // panic(fmt.Errorf(` - Passed argument is not a func(context.Context) neither one of these types: + Passed argument is not a func(iris.Context) neither one of these types: - http.Handler - func(w http.ResponseWriter, r *http.Request) - func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) @@ -64,7 +64,7 @@ func FromStd(handler interface{}) context.Handler { // FromStdWithNext receives a standar handler - middleware form - and returns a // compatible context.Handler wrapper. func FromStdWithNext(h func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)) context.Handler { - return func(ctx context.Context) { + return func(ctx *context.Context) { next := func(w http.ResponseWriter, r *http.Request) { ctx.ResetRequest(r) ctx.Next() diff --git a/core/handlerconv/from_std_test.go b/core/handlerconv/from_std_test.go index 101b2d2561..c024f244fd 100644 --- a/core/handlerconv/from_std_test.go +++ b/core/handlerconv/from_std_test.go @@ -54,7 +54,7 @@ func TestFromStdWithNext(t *testing.T) { } h := handlerconv.FromStdWithNext(stdWNext) - next := func(ctx context.Context) { + next := func(ctx *context.Context) { ctx.WriteString(ctx.Request().Context().Value(contextKey("key")).(string)) } diff --git a/core/host/proxy_test.go b/core/host/proxy_test.go index 78035ce6d9..3ef1886050 100644 --- a/core/host/proxy_test.go +++ b/core/host/proxy_test.go @@ -43,15 +43,15 @@ func TestProxy(t *testing.T) { t.Log(listener.Addr().String()) app := iris.New() - app.Get("/", func(ctx context.Context) { + app.Get("/", func(ctx *context.Context) { ctx.WriteString(expectedIndex) }) - app.Get("/about", func(ctx context.Context) { + app.Get("/about", func(ctx *context.Context) { ctx.WriteString(expectedAbout) }) - app.OnErrorCode(iris.StatusNotFound, func(ctx context.Context) { + app.OnErrorCode(iris.StatusNotFound, func(ctx *context.Context) { ctx.WriteString(unexpectedRoute) }) diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 38bf2b65ed..3250770a95 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -116,7 +116,7 @@ func (repo *repository) register(route *Route, rule RouteRegisterRule) (*Route, return route, nil } -var defaultOverlapFilter = func(ctx context.Context) bool { +var defaultOverlapFilter = func(ctx *context.Context) bool { if ctx.IsStopped() { // It's stopped and the response can be overridden by a new handler. rs, ok := ctx.ResponseWriter().(context.ResponseWriterReseter) @@ -131,7 +131,7 @@ func overlapRoute(r *Route, next *Route) { next.BuildHandlers() nextHandlers := next.Handlers[0:] - decisionHandler := func(ctx context.Context) { + decisionHandler := func(ctx *context.Context) { ctx.Next() if !defaultOverlapFilter(ctx) { @@ -925,7 +925,7 @@ func (api *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) // Returns the GET *Route. func (api *APIBuilder) StaticContent(reqPath string, cType string, content []byte) *Route { modtime := time.Now() - h := func(ctx context.Context) { + h := func(ctx *context.Context) { ctx.ContentType(cType) if _, err := ctx.WriteWithExpiration(content, modtime); err != nil { ctx.StatusCode(http.StatusInternalServerError) @@ -975,7 +975,7 @@ func (api *APIBuilder) Favicon(favPath string, requestPath ...string) *Route { modtime := time.Now() cType := TypeByFilename(favPath) - h := func(ctx context.Context) { + h := func(ctx *context.Context) { ctx.ContentType(cType) if _, err := ctx.WriteWithExpiration(cacheFav, modtime); err != nil { ctx.StatusCode(http.StatusInternalServerError) @@ -1030,7 +1030,7 @@ func (api *APIBuilder) OnAnyErrorCode(handlers ...context.Handler) (routes []*Ro // // Examples: https://github.com/kataras/iris/tree/master/_examples/view func (api *APIBuilder) Layout(tmplLayoutFile string) Party { - api.Use(func(ctx context.Context) { + api.Use(func(ctx *context.Context) { ctx.ViewLayout(tmplLayoutFile) ctx.Next() }) diff --git a/core/router/api_builder_benchmark_test.go b/core/router/api_builder_benchmark_test.go index afb111d2fe..e709b266de 100644 --- a/core/router/api_builder_benchmark_test.go +++ b/core/router/api_builder_benchmark_test.go @@ -73,7 +73,7 @@ func genPaths(routesLength, minCharLength, maxCharLength int) []string { func BenchmarkAPIBuilder(b *testing.B) { rand.Seed(time.Now().Unix()) - noOpHandler := func(ctx context.Context) {} + noOpHandler := func(ctx *context.Context) {} handlersPerRoute := make(context.Handlers, 12) for i := 0; i < cap(handlersPerRoute); i++ { handlersPerRoute[i] = noOpHandler diff --git a/core/router/api_container.go b/core/router/api_container.go index 14e2a4efa8..0504eeb9c8 100644 --- a/core/router/api_container.go +++ b/core/router/api_container.go @@ -42,9 +42,9 @@ func (api *APIContainer) PartyFunc(relativePath string, fn func(*APIContainer)) // Container.GetErrorHandler = func(ctx iris.Context) hero.ErrorHandler { return errorHandler } // // See `RegisterDependency`, `Use`, `Done` and `Handle` too. -func (api *APIContainer) OnError(errorHandler func(context.Context, error)) { +func (api *APIContainer) OnError(errorHandler func(*context.Context, error)) { errHandler := hero.ErrorHandlerFunc(errorHandler) - api.Container.GetErrorHandler = func(ctx context.Context) hero.ErrorHandler { + api.Container.GetErrorHandler = func(ctx *context.Context) hero.ErrorHandler { return errHandler } } diff --git a/core/router/fs.go b/core/router/fs.go index 0ff5a6457a..2e12ac0e73 100644 --- a/core/router/fs.go +++ b/core/router/fs.go @@ -6,7 +6,6 @@ import ( "html" "html/template" "io" - "io/ioutil" "net/http" "net/url" "os" @@ -22,7 +21,7 @@ import ( const indexName = "/index.html" // DirListFunc is the function signature for customizing directory and file listing. -type DirListFunc func(ctx context.Context, dirName string, dir http.File) error +type DirListFunc func(ctx *context.Context, dirOptions DirOptions, dirName string, dir http.File) error // Attachments options for files to be downloaded and saved locally by the client. // See `DirOptions`. @@ -46,7 +45,7 @@ type DirOptions struct { // if end developer does not managed to handle it by hand. IndexName string // When files should served under compression. - Gzip bool + Compress bool // List the files inside the current requested directory if `IndexName` not found. ShowList bool @@ -56,8 +55,6 @@ type DirOptions struct { DirList DirListFunc // Files downloaded and saved locally. - // Gzip option MUST be false in order for this to work. - // TODO(@kataras): find a way to make it work. Attachments Attachments // When embedded. @@ -66,7 +63,7 @@ type DirOptions struct { AssetNames func() []string // called once. // Optional validator that loops through each requested resource. - AssetValidator func(ctx context.Context, name string) bool + AssetValidator func(ctx *context.Context, name string) bool } func getDirOptions(opts ...DirOptions) (options DirOptions) { @@ -80,6 +77,12 @@ func getDirOptions(opts ...DirOptions) (options DirOptions) { options.IndexName = prefix(options.IndexName, "/") } + if !options.Attachments.Enable { + // make sure rate limiting is not used when attachments are not. + options.Attachments.Limit = 0 + options.Attachments.Burst = 0 + } + return } @@ -300,17 +303,16 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { // panic("FileServer: system directory: " + directory + " does not exist") // } - plainStatusCode := func(ctx context.Context, statusCode int) { - if writer, ok := ctx.ResponseWriter().(*context.GzipResponseWriter); ok && writer != nil { - writer.ResetBody() - writer.Disable() + plainStatusCode := func(ctx *context.Context, statusCode int) { + if writer, ok := ctx.ResponseWriter().(*context.CompressResponseWriter); ok { + writer.Disabled = true } ctx.StatusCode(statusCode) } dirList := options.DirList if dirList == nil { - dirList = func(ctx context.Context, dirName string, dir http.File) error { + dirList = func(ctx *context.Context, dirOptions DirOptions, dirName string, dir http.File) error { dirs, err := dir.Readdir(-1) if err != nil { return err @@ -323,6 +325,7 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { if err != nil { return err } + for _, d := range dirs { name := d.Name() if d.IsDir() { @@ -341,10 +344,14 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { Path: upath, } // edit here to redirect correctly, standard library misses that. + downloadAttr := "" + if dirOptions.Attachments.Enable && !d.IsDir() { + downloadAttr = " download" // fixes chrome Resource interpreted, other browsers will just ignore this attribute. + } // name may contain '?' or '#', which must be escaped to remain // part of the URL path, and not indicate the start of a query // string or fragment. - _, err = ctx.Writef("%s\n", url.String(), html.EscapeString(name)) + _, err = ctx.Writef("%s\n", url.String(), downloadAttr, html.EscapeString(name)) if err != nil { return err } @@ -354,17 +361,10 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { } } - h := func(ctx context.Context) { + h := func(ctx *context.Context) { name := prefix(ctx.Request().URL.Path, "/") ctx.Request().URL.Path = name - gzip := options.Gzip - if !gzip { - // if false then check if the dev did something like `ctx.Gzip(true)`. - _, gzip = ctx.ResponseWriter().(*context.GzipResponseWriter) - } - // ctx.Gzip(options.Gzip) - f, err := fs.Open(name) if err != nil { plainStatusCode(ctx, http.StatusNotFound) @@ -378,6 +378,7 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { return } + indexFound := false // use contents of index.html for directory, if present if info.IsDir() && options.IndexName != "" { // Note that, in contrast of the default net/http mechanism; @@ -397,6 +398,7 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { if err == nil { info = infoIndex f = fIndex + indexFound = true } } } @@ -414,7 +416,7 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { return } ctx.SetLastModified(info.ModTime()) - err = dirList(ctx, info.Name(), f) + err = dirList(ctx, options, info.Name(), f) if err != nil { ctx.Application().Logger().Errorf("FileServer: dirList: %v", err) plainStatusCode(ctx, http.StatusInternalServerError) @@ -451,32 +453,10 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { // and the binary data inside "f". detectOrWriteContentType(ctx, info.Name(), f) - if gzip { - // set the last modified as "serveContent" does. - ctx.SetLastModified(info.ModTime()) - - // write the file to the response writer. - contents, err := ioutil.ReadAll(f) - if err != nil { - ctx.Application().Logger().Debugf("err reading file: %v", err) - plainStatusCode(ctx, http.StatusInternalServerError) - return - } - - // Use `WriteNow` instead of `Write` - // because we need to know the compressed written size before - // the `FlushResponse`. - _, err = ctx.GzipResponseWriter().Write(contents) - if err != nil { - ctx.Application().Logger().Debugf("short write: %v", err) - plainStatusCode(ctx, http.StatusInternalServerError) - return - } - return - } - - if options.Attachments.Enable { + // if not index file and attachments should be force-sent: + if !indexFound && options.Attachments.Enable { destName := info.Name() + // diposition := "attachment" if nameFunc := options.Attachments.NameFunc; nameFunc != nil { destName = nameFunc(destName) } @@ -484,6 +464,14 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { ctx.ResponseWriter().Header().Set(context.ContentDispositionHeaderKey, "attachment;filename="+destName) } + ctx.Compress(options.Compress) + // if gzip { + // ctx.Compress(true) + // context.AddCompressHeaders(ctx.ResponseWriter().Header()) + // // to not write the content-length( see http.serveContent): + // // ctx.ResponseWriter().Header().Set(context.ContentEncodingHeaderKey, context.GzipHeaderValue) + // } + // 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) { @@ -520,7 +508,7 @@ func StripPrefix(prefix string, h context.Handler) context.Handler { } canonicalPrefix = toWebPath(canonicalPrefix) - return func(ctx context.Context) { + return func(ctx *context.Context) { if p := strings.TrimPrefix(ctx.Request().URL.Path, canonicalPrefix); len(p) < len(ctx.Request().URL.Path) { ctx.Request().URL.Path = p h(ctx) @@ -551,7 +539,7 @@ func Abs(path string) string { // The algorithm uses at most sniffLen bytes to make its decision. const sniffLen = 512 -func detectOrWriteContentType(ctx context.Context, name string, content io.ReadSeeker) (string, error) { +func detectOrWriteContentType(ctx *context.Context, name string, content io.ReadSeeker) (string, error) { // If Content-Type isn't set, use the file's extension to find it, but // if the Content-Type is unset explicitly, do not sniff the type. ctypes, haveType := ctx.ResponseWriter().Header()["Content-Type"] @@ -580,7 +568,7 @@ func detectOrWriteContentType(ctx context.Context, name string, content io.ReadS // localRedirect gives a Moved Permanently response. // It does not convert relative paths to absolute paths like Redirect does. -func localRedirect(ctx context.Context, newPath string) { +func localRedirect(ctx *context.Context, newPath string) { if q := ctx.Request().URL.RawQuery; q != "" { newPath += "?" + q } @@ -619,7 +607,7 @@ func DirListRich(opts ...DirListRichOptions) DirListFunc { options.Tmpl = DirListRichTemplate } - return func(ctx context.Context, dirName string, dir http.File) error { + return func(ctx *context.Context, dirOptions DirOptions, dirName string, dir http.File) error { dirs, err := dir.Readdir(-1) if err != nil { return err @@ -655,12 +643,14 @@ func DirListRich(opts ...DirListRichOptions) DirListFunc { url := url.URL{Path: upath} + shouldDownload := dirOptions.Attachments.Enable && !d.IsDir() pageData.Files = append(pageData.Files, fileInfoData{ - Info: d, - ModTime: d.ModTime().UTC().Format(http.TimeFormat), - Path: url.String(), - RelPath: path.Join(ctx.Path(), name), - Name: html.EscapeString(name), + Info: d, + ModTime: d.ModTime().UTC().Format(http.TimeFormat), + Path: url.String(), + RelPath: path.Join(ctx.Path(), name), + Name: html.EscapeString(name), + Download: shouldDownload, }) } @@ -679,11 +669,12 @@ type ( } fileInfoData struct { - Info os.FileInfo - ModTime string // format-ed time. - Path string // the request path. - RelPath string // file path without the system directory itself (we are not exposing it to the user). - Name string // the html-escaped name. + Info os.FileInfo + ModTime string // format-ed time. + Path string // the request path. + RelPath string // file path without the system directory itself (we are not exposing it to the user). + Name string // the html-escaped name. + Download bool // the file should be downloaded (attachment instead of inline view). } ) @@ -786,7 +777,11 @@ var DirListRichTemplate = template.Must(template.New("dirlist"). {{ range $idx, $file := .Files }} {{ $idx }} - {{ $file.Name }} + {{ if $file.Download }} + {{ $file.Name }} + {{ else }} + {{ $file.Name }} + {{ end }} {{ if $file.Info.IsDir }} Dir {{ else }} diff --git a/core/router/handler.go b/core/router/handler.go index ccf15cd481..97ed3074e0 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -25,11 +25,11 @@ type ( HTTPErrorHandler // HandleRequest should handle the request based on the Context. - HandleRequest(ctx context.Context) + HandleRequest(ctx *context.Context) // Build should builds the handler, it's being called on router's BuildRouter. Build(provider RoutesProvider) error // RouteExists reports whether a particular route exists. - RouteExists(ctx context.Context, method, path string) bool + RouteExists(ctx *context.Context, method, path string) bool } // HTTPErrorHandler should contain a method `FireErrorCode` which @@ -37,7 +37,7 @@ type ( HTTPErrorHandler interface { // FireErrorCode should send an error response to the client based // on the given context's response status code. - FireErrorCode(ctx context.Context) + FireErrorCode(ctx *context.Context) } ) @@ -294,7 +294,7 @@ func bindMultiParamTypesHandler(r *Route) { currentStatusCode = http.StatusOK } - decisionHandler := func(ctx context.Context) { + decisionHandler := func(ctx *context.Context) { // println("core/router/handler.go: decision handler; " + ctx.Path() + " route.Name: " + r.Name + " vs context's " + ctx.GetCurrentRoute().Name()) currentRoute := ctx.GetCurrentRoute() @@ -318,7 +318,7 @@ func bindMultiParamTypesHandler(r *Route) { r.topLink.beginHandlers = append(context.Handlers{decisionHandler}, r.topLink.beginHandlers...) } -func (h *routerHandler) canHandleSubdomain(ctx context.Context, subdomain string) bool { +func (h *routerHandler) canHandleSubdomain(ctx *context.Context, subdomain string) bool { if subdomain == "" { return true } @@ -356,7 +356,7 @@ func (h *routerHandler) canHandleSubdomain(ctx context.Context, subdomain string return true } -func (h *routerHandler) HandleRequest(ctx context.Context) { +func (h *routerHandler) HandleRequest(ctx *context.Context) { method := ctx.Method() path := ctx.Path() config := h.config // ctx.Application().GetConfigurationReadOnly() @@ -445,18 +445,18 @@ func statusCodeSuccessful(statusCode int) bool { // FireErrorCode handles the response's error response. // If `Configuration.ResetOnFireErrorCode()` is true -// and the response writer was a recorder or a gzip writer one +// and the response writer was a recorder one // then it will try to reset the headers and the body before calling the // registered (or default) error handler for that error code set by // `ctx.StatusCode` method. -func (h *routerHandler) FireErrorCode(ctx context.Context) { +func (h *routerHandler) FireErrorCode(ctx *context.Context) { // On common response writer, always check // if we can't reset the body and the body has been filled // which means that the status code already sent, // then do not fire this custom error code, // rel: context/context.go#EndRequest. // - // Note that, this is set to 0 on recorder and gzip writer because they cache the response, + // Note that, this is set to 0 on recorder because it holds the response before sent, // so we check their len(Body()) instead, look below. if ctx.ResponseWriter().Written() > 0 { return @@ -473,21 +473,17 @@ func (h *routerHandler) FireErrorCode(ctx context.Context) { // reset if previous content and it's recorder, keep the status code. w.ClearHeaders() w.ResetBody() - } else if w, ok := ctx.ResponseWriter().(*context.GzipResponseWriter); ok { + } else if w, ok := ctx.ResponseWriter().(*context.CompressResponseWriter); ok { // reset and disable the gzip in order to be an expected form of http error result - w.ResetBody() - w.Disable() + w.Disabled = true } } else { - // check if a body already set (the error response is handled by the handler itself, see `Context.EndRequest`) + // check if a body already set (the error response is handled by the handler itself, + // see `Context.EndRequest`) if w, ok := ctx.IsRecording(); ok { if len(w.Body()) > 0 { return } - } else if w, ok := ctx.ResponseWriter().(*context.GzipResponseWriter); ok { - if len(w.Body()) > 0 { - return - } } } @@ -526,7 +522,7 @@ func (h *routerHandler) FireErrorCode(ctx context.Context) { // because may the user want to add a fallback error code // i.e // users := app.Party("/users") - // users.Done(func(ctx context.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }}) + // users.Done(func(ctx *context.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }}) // use .HandlerIndex // that sets the current handler index to zero @@ -556,7 +552,7 @@ func (h *routerHandler) FireErrorCode(ctx context.Context) { ctx.WriteString(context.StatusText(statusCode)) } -func (h *routerHandler) subdomainAndPathAndMethodExists(ctx context.Context, t *trie, method, path string) bool { +func (h *routerHandler) subdomainAndPathAndMethodExists(ctx *context.Context, t *trie, method, path string) bool { if method != "" && method != t.method { return false } @@ -599,7 +595,7 @@ func (h *routerHandler) subdomainAndPathAndMethodExists(ctx context.Context, t * // RouteExists reports whether a particular route exists // It will search from the current subdomain of context's host, if not inside the root domain. -func (h *routerHandler) RouteExists(ctx context.Context, method, path string) bool { +func (h *routerHandler) RouteExists(ctx *context.Context, method, path string) bool { for i := range h.trees { t := h.trees[i] if h.subdomainAndPathAndMethodExists(ctx, t, method, path) { diff --git a/core/router/handler_execution_rules.go b/core/router/handler_execution_rules.go index ac2a8af978..3ea0b06955 100644 --- a/core/router/handler_execution_rules.go +++ b/core/router/handler_execution_rules.go @@ -72,7 +72,7 @@ func (e ExecutionOptions) buildHandler(h context.Handler) context.Handler { return h } - return func(ctx context.Context) { + return func(ctx *context.Context) { // Proceed will fire the handler and return false here if it doesn't contain a `ctx.Next()`, // so we add the `ctx.Next()` wherever is necessary in order to eliminate any dev's misuse. if !ctx.Proceed(h) { diff --git a/core/router/handler_execution_rules_test.go b/core/router/handler_execution_rules_test.go index 035a989a3e..ae65a80053 100644 --- a/core/router/handler_execution_rules_test.go +++ b/core/router/handler_execution_rules_test.go @@ -19,7 +19,7 @@ var ( ) func writeStringHandler(text string, withNext bool) context.Handler { - return func(ctx context.Context) { + return func(ctx *context.Context) { ctx.WriteString(text) if withNext { ctx.Next() diff --git a/core/router/party.go b/core/router/party.go index 73028d5e22..c124235fe4 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -154,7 +154,10 @@ type Party interface { // // Alternatively, to get just the handler for that look the FileServer function instead. // - // api.HandleDir("/static", "./assets", DirOptions {ShowList: true, Gzip: true, IndexName: "index.html"}) + // api.HandleDir("/static", "./assets", DirOptions { + // ShowList: true, + // Compress: true, + // IndexName: "index.html"}) // // Returns all the registered routes, including GET index and path patterm and HEAD. // diff --git a/core/router/router.go b/core/router/router.go index 60ad02012e..ac02caca2a 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -194,7 +194,7 @@ func (router *Router) WrapRouter(wrapperFunc WrapperFunc) { } // ServeHTTPC serves the raw context, useful if we have already a context, it by-pass the wrapper. -func (router *Router) ServeHTTPC(ctx context.Context) { +func (router *Router) ServeHTTPC(ctx *context.Context) { router.requestHandler.HandleRequest(ctx) } @@ -204,6 +204,6 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { // RouteExists reports whether a particular route exists // It will search from the current subdomain of context's host, if not inside the root domain. -func (router *Router) RouteExists(ctx context.Context, method, path string) bool { +func (router *Router) RouteExists(ctx *context.Context, method, path string) bool { return router.requestHandler.RouteExists(ctx, method, path) } diff --git a/core/router/router_handlers_order_test.go b/core/router/router_handlers_order_test.go index a14fd4ea95..02c52b4ee2 100644 --- a/core/router/router_handlers_order_test.go +++ b/core/router/router_handlers_order_test.go @@ -20,43 +20,43 @@ import ( // response should be the same at all cases. var ( mainResponse = "main" - mainHandler = func(ctx context.Context) { + mainHandler = func(ctx *context.Context) { ctx.WriteString(mainResponse) ctx.Next() } firstUseResponse = "use1" - firstUseHandler = func(ctx context.Context) { + firstUseHandler = func(ctx *context.Context) { ctx.WriteString(firstUseResponse) ctx.Next() } secondUseResponse = "use2" - secondUseHandler = func(ctx context.Context) { + secondUseHandler = func(ctx *context.Context) { ctx.WriteString(secondUseResponse) ctx.Next() } firstUseGlobalResponse = "useglobal1" - firstUseGlobalHandler = func(ctx context.Context) { + firstUseGlobalHandler = func(ctx *context.Context) { ctx.WriteString(firstUseGlobalResponse) ctx.Next() } secondUseGlobalResponse = "useglobal2" - secondUseGlobalHandler = func(ctx context.Context) { + secondUseGlobalHandler = func(ctx *context.Context) { ctx.WriteString(secondUseGlobalResponse) ctx.Next() } firstDoneResponse = "done1" - firstDoneHandler = func(ctx context.Context) { + firstDoneHandler = func(ctx *context.Context) { ctx.WriteString(firstDoneResponse) ctx.Next() } secondDoneResponse = "done2" - secondDoneHandler = func(ctx context.Context) { + secondDoneHandler = func(ctx *context.Context) { ctx.WriteString(secondDoneResponse) } diff --git a/core/router/router_test.go b/core/router/router_test.go index 1be02bc129..535cb7bd33 100644 --- a/core/router/router_test.go +++ b/core/router/router_test.go @@ -13,14 +13,14 @@ import ( func TestRouteExists(t *testing.T) { // build the api app := iris.New() - emptyHandler := func(context.Context) {} + emptyHandler := func(*context.Context) {} // setup the tested routes app.Handle("GET", "/route-exists", emptyHandler) app.Handle("POST", "/route-with-param/{param}", emptyHandler) // check RouteExists - app.Handle("GET", "/route-test", func(ctx context.Context) { + app.Handle("GET", "/route-test", func(ctx *context.Context) { if ctx.RouteExists("GET", "/route-not-exists") { t.Error("Route with path should not exists") } diff --git a/core/router/router_wildcard_root_test.go b/core/router/router_wildcard_root_test.go index 0d5f68cafa..becfc3985a 100644 --- a/core/router/router_wildcard_root_test.go +++ b/core/router/router_wildcard_root_test.go @@ -36,18 +36,18 @@ type testRoute struct { requests []testRouteRequest } -var h = func(ctx context.Context) { +var h = func(ctx *context.Context) { ctx.WriteString(ctx.Path()) } -var h2 = func(ctx context.Context) { +var h2 = func(ctx *context.Context) { ctx.StatusCode(iris.StatusForbidden) // ! 200 but send the body as expected, // we need that kind of behavior to determinate which handler is executed for routes that // both having wildcard path but first one is registered on root level. ctx.WriteString(ctx.Path()) } -func h3(ctx context.Context) { +func h3(ctx *context.Context) { ctx.Writef(staticPathPrefixBody + ctx.Path()) } diff --git a/core/router/status_test.go b/core/router/status_test.go index 12fee501db..8a4a109fe8 100644 --- a/core/router/status_test.go +++ b/core/router/status_test.go @@ -12,7 +12,7 @@ import ( "github.com/kataras/iris/v12/httptest" ) -var defaultErrHandler = func(ctx context.Context) { +var defaultErrHandler = func(ctx *context.Context) { text := http.StatusText(ctx.GetStatusCode()) ctx.WriteString(text) } @@ -25,18 +25,18 @@ func TestOnAnyErrorCode(t *testing.T) { expectedPrintBeforeExecuteErr := "printed before error" // with a middleware - app.OnAnyErrorCode(func(ctx context.Context) { + app.OnAnyErrorCode(func(ctx *context.Context) { buff.WriteString(expectedPrintBeforeExecuteErr) ctx.Next() }, defaultErrHandler) expectedFoundResponse := "found" - app.Get("/found", func(ctx context.Context) { + app.Get("/found", func(ctx *context.Context) { ctx.WriteString(expectedFoundResponse) }) expected407 := "this should be sent, we manage the response response by ourselves" - app.Get("/407", func(ctx context.Context) { + app.Get("/407", func(ctx *context.Context) { ctx.Record() ctx.WriteString(expected407) ctx.StatusCode(iris.StatusProxyAuthRequired) @@ -64,12 +64,12 @@ func TestOnAnyErrorCode(t *testing.T) { app2 := iris.New() app2.Configure(iris.WithResetOnFireErrorCode) - app2.OnAnyErrorCode(func(ctx context.Context) { + app2.OnAnyErrorCode(func(ctx *context.Context) { buff.WriteString(expectedPrintBeforeExecuteErr) ctx.Next() }, defaultErrHandler) - app2.Get("/406", func(ctx context.Context) { + app2.Get("/406", func(ctx *context.Context) { ctx.Record() ctx.WriteString("this should not be sent, only status text will be sent") ctx.WriteString("the handler can handle 'rollback' of the text when error code fired because of the recorder") diff --git a/go.mod b/go.mod index 59982028e6..c3669d9c0e 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/BurntSushi/toml v0.3.1 github.com/CloudyKit/jet/v4 v4.0.0 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 github.com/dgraph-io/badger/v2 v2.0.3 github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 @@ -23,6 +24,7 @@ require ( github.com/kataras/neffos v0.0.16 github.com/kataras/pio v0.0.8 github.com/kataras/sitemap v0.0.5 + github.com/kataras/tunnel v0.0.1 github.com/klauspost/compress v1.10.10 github.com/mediocregopher/radix/v3 v3.5.2 github.com/microcosm-cc/bluemonday v1.0.3 @@ -32,7 +34,7 @@ require ( github.com/square/go-jose/v3 v3.0.0-20200630053402-0a67ce9b0693 github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1 go.etcd.io/bbolt v1.3.5 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 golang.org/x/net v0.0.0-20200707034311-ab3426394381 golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae golang.org/x/text v0.3.3 diff --git a/hero/binding.go b/hero/binding.go index 2327901b43..4944e40ddb 100644 --- a/hero/binding.go +++ b/hero/binding.go @@ -176,7 +176,7 @@ func getBindingsFor(inputs []reflect.Type, deps []*Dependency, paramsCount int) // wrap the existing dependency handler. paramHandler := paramDependencyHandler(getParamIndex()) prevHandler := d.Handle - d.Handle = func(ctx context.Context, input *Input) (reflect.Value, error) { + d.Handle = func(ctx *context.Context, input *Input) (reflect.Value, error) { v, err := paramHandler(ctx, input) if err != nil { v, err = prevHandler(ctx, input) @@ -315,7 +315,7 @@ func paramBinding(index, paramIndex int, typ reflect.Type) *binding { } func paramDependencyHandler(paramIndex int) DependencyHandler { - return func(ctx context.Context, input *Input) (reflect.Value, error) { + return func(ctx *context.Context, input *Input) (reflect.Value, error) { if ctx.Params().Len() <= paramIndex { return emptyValue, ErrSeeOther } @@ -329,7 +329,7 @@ func paramDependencyHandler(paramIndex int) DependencyHandler { func payloadBinding(index int, typ reflect.Type) *binding { return &binding{ Dependency: &Dependency{ - Handle: func(ctx context.Context, input *Input) (newValue reflect.Value, err error) { + Handle: func(ctx *context.Context, input *Input) (newValue reflect.Value, err error) { wasPtr := input.Type.Kind() == reflect.Ptr if serveDepsV := ctx.Values().Get(context.DependenciesContextKey); serveDepsV != nil { diff --git a/hero/binding_test.go b/hero/binding_test.go index d9c5b932c6..b9ef84495c 100644 --- a/hero/binding_test.go +++ b/hero/binding_test.go @@ -45,9 +45,9 @@ func TestGetBindingsForFunc(t *testing.T) { var testRequestTyp = reflect.TypeOf(testRequest{}) var deps = []*Dependency{ - NewDependency(func(ctx context.Context) testRequest { return testRequest{Email: "should be ignored"} }), + NewDependency(func(ctx *context.Context) testRequest { return testRequest{Email: "should be ignored"} }), NewDependency(42), - NewDependency(func(ctx context.Context) (v testRequest, err error) { + NewDependency(func(ctx *context.Context) (v testRequest, err error) { err = ctx.ReadJSON(&v) return }), @@ -55,7 +55,7 @@ func TestGetBindingsForFunc(t *testing.T) { NewDependency("should not be ignored when requested"), // Dependencies like these should always be registered last. - NewDependency(func(ctx context.Context, input *Input) (newValue reflect.Value, err error) { + NewDependency(func(ctx *context.Context, input *Input) (newValue reflect.Value, err error) { wasPtr := input.Type.Kind() == reflect.Ptr newValue = reflect.New(indirectType(input.Type)) @@ -75,19 +75,19 @@ func TestGetBindingsForFunc(t *testing.T) { Expected []*binding }{ { // 0 - Func: func(ctx context.Context) { + Func: func(ctx *context.Context) { ctx.WriteString("t1") }, Expected: []*binding{contextBinding(0)}, }, { // 1 - Func: func(ctx context.Context) error { + Func: func(ctx *context.Context) error { return fmt.Errorf("err1") }, Expected: []*binding{contextBinding(0)}, }, { // 2 - Func: func(ctx context.Context) testResponse { + Func: func(ctx *context.Context) testResponse { return testResponse{Name: "name"} }, Expected: []*binding{contextBinding(0)}, @@ -105,19 +105,19 @@ func TestGetBindingsForFunc(t *testing.T) { Expected: []*binding{{Dependency: deps[2], Input: &Input{Index: 0, Type: testRequestTyp}}}, }, { // 5 - Func: func(ctx context.Context, in testRequest) testResponse { + Func: func(ctx *context.Context, in testRequest) testResponse { return testResponse{Name: "(with ctx) email of " + in.Email} }, Expected: []*binding{contextBinding(0), {Dependency: deps[2], Input: &Input{Index: 1, Type: testRequestTyp}}}, }, { // 6 - Func: func(in testRequest, ctx context.Context) testResponse { // reversed. + Func: func(in testRequest, ctx *context.Context) testResponse { // reversed. return testResponse{Name: "(with ctx) email of " + in.Email} }, Expected: []*binding{{Dependency: deps[2], Input: &Input{Index: 0, Type: testRequestTyp}}, contextBinding(1)}, }, { // 7 - Func: func(in testRequest, ctx context.Context, in2 string) testResponse { // reversed. + Func: func(in testRequest, ctx *context.Context, in2 string) testResponse { // reversed. return testResponse{Name: "(with ctx) email of " + in.Email + "and in2: " + in2} }, Expected: []*binding{ @@ -133,7 +133,7 @@ func TestGetBindingsForFunc(t *testing.T) { }, }, { // 8 - Func: func(in testRequest, ctx context.Context, in2, in3 string) testResponse { // reversed. + Func: func(in testRequest, ctx *context.Context, in2, in3 string) testResponse { // reversed. return testResponse{Name: "(with ctx) email of " + in.Email + " | in2: " + in2 + " in3: " + in3} }, Expected: []*binding{ @@ -153,7 +153,7 @@ func TestGetBindingsForFunc(t *testing.T) { }, }, { // 9 - Func: func(ctx context.Context, in testRequest, in2 testRequest2) testResponse { + Func: func(ctx *context.Context, in testRequest, in2 testRequest2) testResponse { return testResponse{Name: fmt.Sprintf("(with ctx) email of %s and in2.Age %d", in.Email, in2.Age)} }, Expected: []*binding{ @@ -363,7 +363,7 @@ func TestBindingsForStruct(t *testing.T) { } var depsInterfaces = []*Dependency{ - NewDependency(func(ctx context.Context) interface{} { + NewDependency(func(ctx *context.Context) interface{} { return "name" }), } diff --git a/hero/container.go b/hero/container.go index ab08b1a0ea..6def3fbb29 100644 --- a/hero/container.go +++ b/hero/container.go @@ -32,7 +32,7 @@ type Container struct { Dependencies []*Dependency // GetErrorHandler should return a valid `ErrorHandler` to handle bindings AND handler dispatch errors. // Defaults to a functon which returns the `DefaultErrorHandler`. - GetErrorHandler func(context.Context) ErrorHandler // cannot be nil. + GetErrorHandler func(*context.Context) ErrorHandler // cannot be nil. // resultHandlers is a list of functions that serve the return struct value of a function handler. // Defaults to "defaultResultHandler" but it can be overridden. @@ -43,13 +43,13 @@ type Container struct { // Contains the iris context, standard context, iris sessions and time dependencies. var BuiltinDependencies = []*Dependency{ // iris context dependency. - NewDependency(func(ctx context.Context) context.Context { return ctx }).Explicitly(), + NewDependency(func(ctx *context.Context) *context.Context { return ctx }).Explicitly(), // standard context dependency. - NewDependency(func(ctx context.Context) stdContext.Context { + NewDependency(func(ctx *context.Context) stdContext.Context { return ctx.Request().Context() }).Explicitly(), // iris session dependency. - NewDependency(func(ctx context.Context) *sessions.Session { + NewDependency(func(ctx *context.Context) *sessions.Session { session := sessions.Get(ctx) if session == nil { panic("binding: session is nil - app.Use(sess.Handler()) to fix it") @@ -58,19 +58,19 @@ var BuiltinDependencies = []*Dependency{ return session }).Explicitly(), // time.Time to time.Now dependency. - NewDependency(func(ctx context.Context) time.Time { + NewDependency(func(ctx *context.Context) time.Time { return time.Now() }).Explicitly(), // standard http Request dependency. - NewDependency(func(ctx context.Context) *http.Request { + NewDependency(func(ctx *context.Context) *http.Request { return ctx.Request() }).Explicitly(), // standard http ResponseWriter dependency. - NewDependency(func(ctx context.Context) http.ResponseWriter { + NewDependency(func(ctx *context.Context) http.ResponseWriter { return ctx.ResponseWriter() }).Explicitly(), // http headers dependency. - NewDependency(func(ctx context.Context) http.Header { + NewDependency(func(ctx *context.Context) http.Header { return ctx.Request().Header }).Explicitly(), // payload and param bindings are dynamically allocated and declared at the end of the `binding` source file. @@ -86,7 +86,7 @@ func New(dependencies ...interface{}) *Container { c := &Container{ Sorter: sortByNumMethods, Dependencies: deps, - GetErrorHandler: func(context.Context) ErrorHandler { + GetErrorHandler: func(*context.Context) ErrorHandler { return DefaultErrorHandler }, } diff --git a/hero/dependency.go b/hero/dependency.go index 509d6f95ec..9fb0b920a6 100644 --- a/hero/dependency.go +++ b/hero/dependency.go @@ -10,7 +10,7 @@ import ( type ( // DependencyHandler is the native function declaration which implementors should return a value match to an input. - DependencyHandler func(ctx context.Context, input *Input) (reflect.Value, error) + DependencyHandler func(ctx *context.Context, input *Input) (reflect.Value, error) // Dependency describes the design-time dependency to be injected at serve time. // Contains its source location, the dependency handler (provider) itself and information // such as static for static struct values or explicit to bind a value to its exact DestType and not if just assignable to it (interfaces). @@ -92,11 +92,11 @@ func fromDependencyHandler(_ reflect.Value, dest *Dependency) bool { dependency := dest.OriginalValue handler, ok := dependency.(DependencyHandler) if !ok { - handler, ok = dependency.(func(context.Context, *Input) (reflect.Value, error)) + handler, ok = dependency.(func(*context.Context, *Input) (reflect.Value, error)) if !ok { // It's almost a handler, only the second `Input` argument is missing. - if h, is := dependency.(func(context.Context) (reflect.Value, error)); is { - handler = func(ctx context.Context, _ *Input) (reflect.Value, error) { + if h, is := dependency.(func(*context.Context) (reflect.Value, error)); is { + handler = func(ctx *context.Context, _ *Input) (reflect.Value, error) { return h(ctx) } ok = is @@ -114,7 +114,7 @@ func fromDependencyHandler(_ reflect.Value, dest *Dependency) bool { func fromStructValue(v reflect.Value, dest *Dependency) bool { if !isFunc(v) { // It's just a static value. - handler := func(context.Context, *Input) (reflect.Value, error) { + handler := func(*context.Context, *Input) (reflect.Value, error) { return v, nil } @@ -183,7 +183,7 @@ func handlerFromFunc(v reflect.Value, typ reflect.Type) DependencyHandler { hasErrorOut := typ.NumOut() == 2 // if two, always an error type here. hasInputIn := typ.NumIn() == 2 && typ.In(1) == inputTyp - return func(ctx context.Context, input *Input) (reflect.Value, error) { + return func(ctx *context.Context, input *Input) (reflect.Value, error) { inputs := ctx.ReflectValue() if hasInputIn { inputs = append(inputs, input.selfValue) @@ -214,7 +214,7 @@ func fromDependentFunc(v reflect.Value, dest *Dependency, funcDependencies []*De firstOutIsError := numOut == 1 && isError(typ.Out(0)) secondOutIsError := numOut == 2 && isError(typ.Out(1)) - handler := func(ctx context.Context, _ *Input) (reflect.Value, error) { + handler := func(ctx *context.Context, _ *Input) (reflect.Value, error) { inputs := make([]reflect.Value, numIn) for _, binding := range bindings { diff --git a/hero/dependency_test.go b/hero/dependency_test.go index a8c7ff4154..71ee7c7a5f 100644 --- a/hero/dependency_test.go +++ b/hero/dependency_test.go @@ -25,54 +25,54 @@ func TestDependency(t *testing.T) { Expected: struct{ Name string }{"name"}, }, { - Dependency: func(context.Context, *Input) (reflect.Value, error) { + Dependency: func(*context.Context, *Input) (reflect.Value, error) { return reflect.ValueOf(42), nil }, Expected: 42, }, { - Dependency: DependencyHandler(func(context.Context, *Input) (reflect.Value, error) { + Dependency: DependencyHandler(func(*context.Context, *Input) (reflect.Value, error) { return reflect.ValueOf(255), nil }), Expected: 255, }, { - Dependency: func(context.Context) (reflect.Value, error) { + Dependency: func(*context.Context) (reflect.Value, error) { return reflect.ValueOf("OK without Input"), nil }, Expected: "OK without Input", }, { - Dependency: func(context.Context, ...string) (reflect.Value, error) { + Dependency: func(*context.Context, ...string) (reflect.Value, error) { return reflect.ValueOf("OK variadic ignored"), nil }, Expected: "OK variadic ignored", }, { - Dependency: func(context.Context) reflect.Value { + Dependency: func(*context.Context) reflect.Value { return reflect.ValueOf("OK without Input and error") }, Expected: "OK without Input and error", }, { - Dependency: func(context.Context, ...int) reflect.Value { + Dependency: func(*context.Context, ...int) reflect.Value { return reflect.ValueOf("OK without error and variadic ignored") }, Expected: "OK without error and variadic ignored", }, { - Dependency: func(context.Context) interface{} { + Dependency: func(*context.Context) interface{} { return "1" }, Expected: "1", }, { - Dependency: func(context.Context) interface{} { + Dependency: func(*context.Context) interface{} { return false }, Expected: false, diff --git a/hero/func_result.go b/hero/func_result.go index b6cc5bcf7c..ef7c1e45f8 100644 --- a/hero/func_result.go +++ b/hero/func_result.go @@ -11,9 +11,9 @@ import ( ) // ResultHandler describes the function type which should serve the "v" struct value. -type ResultHandler func(ctx context.Context, v interface{}) error +type ResultHandler func(ctx *context.Context, v interface{}) error -func defaultResultHandler(ctx context.Context, v interface{}) error { +func defaultResultHandler(ctx *context.Context, v interface{}) error { if p, ok := v.(PreflightResult); ok { if err := p.Preflight(ctx); err != nil { return err @@ -57,7 +57,7 @@ func defaultResultHandler(ctx context.Context, v interface{}) error { // Example at: https://github.com/kataras/iris/tree/master/_examples/dependency-injection/overview. type Result interface { // Dispatch should send a response to the client. - Dispatch(context.Context) + Dispatch(*context.Context) } // PreflightResult is an interface which implementers @@ -73,7 +73,7 @@ type Result interface { // The caller can manage it at the handler itself. However, // to reduce thoese type of duplications it's preferable to use such a standard interface instead. type PreflightResult interface { - Preflight(context.Context) error + Preflight(*context.Context) error } var defaultFailureResponse = Response{Code: DefaultErrStatusCode} @@ -115,7 +115,7 @@ type compatibleErr interface { } // dispatchErr writes the error to the response. -func dispatchErr(ctx context.Context, status int, err error) bool { +func dispatchErr(ctx *context.Context, status int, err error) bool { if err == nil { return false } @@ -154,7 +154,7 @@ func dispatchErr(ctx context.Context, status int, err error) bool { // Result or (Result, error) and so on... // // where Get is an HTTP METHOD. -func dispatchFuncResult(ctx context.Context, values []reflect.Value, handler ResultHandler) error { +func dispatchFuncResult(ctx *context.Context, values []reflect.Value, handler ResultHandler) error { if len(values) == 0 { return nil } @@ -324,7 +324,7 @@ func dispatchFuncResult(ctx context.Context, values []reflect.Value, handler Res // dispatchCommon is being used internally to send // commonly used data to the response writer with a smart way. -func dispatchCommon(ctx context.Context, +func dispatchCommon(ctx *context.Context, statusCode int, contentType string, content []byte, v interface{}, handler ResultHandler, found bool) error { // if we have a false boolean as a return value // then skip everything and fire a not found, @@ -416,7 +416,7 @@ type Response struct { var _ Result = Response{} // Dispatch writes the response result to the context's response writer. -func (r Response) Dispatch(ctx context.Context) { +func (r Response) Dispatch(ctx *context.Context) { if dispatchErr(ctx, r.Code, r.Err) { return } @@ -492,7 +492,7 @@ func ensureExt(s string) string { // Dispatch writes the template filename, template layout and (any) data to the client. // Completes the `Result` interface. -func (r View) Dispatch(ctx context.Context) { // r as Response view. +func (r View) Dispatch(ctx *context.Context) { // r as Response view. if dispatchErr(ctx, r.Code, r.Err) { return } @@ -520,9 +520,7 @@ func (r View) Dispatch(ctx context.Context) { // r as Response view. // else check if r.Data is map or struct, if struct convert it to map, // do a range loop and modify the data one by one. // context.Map is actually a map[string]interface{} but we have to make that check: - if m, ok := r.Data.(map[string]interface{}); ok { - setViewData(ctx, m) - } else if m, ok := r.Data.(context.Map); ok { + if m, ok := r.Data.(context.Map); ok { setViewData(ctx, m) } else if reflect.Indirect(reflect.ValueOf(r.Data)).Kind() == reflect.Struct { setViewData(ctx, structs.Map(r)) @@ -534,7 +532,7 @@ func (r View) Dispatch(ctx context.Context) { // r as Response view. } } -func setViewData(ctx context.Context, data map[string]interface{}) { +func setViewData(ctx *context.Context, data map[string]interface{}) { for k, v := range data { ctx.ViewData(k, v) } diff --git a/hero/handler.go b/hero/handler.go index 113a7017b8..673a8ece3d 100644 --- a/hero/handler.go +++ b/hero/handler.go @@ -13,15 +13,15 @@ type ( // Handles non-nil errors return from a hero handler or a controller's method (see `getBindingsFor` and `Handler`) // the error may return from a request-scoped dependency too (see `Handler`). ErrorHandler interface { - HandleError(context.Context, error) + HandleError(*context.Context, error) } // ErrorHandlerFunc implements the `ErrorHandler`. // It describes the type defnition for an error function handler. - ErrorHandlerFunc func(context.Context, error) + ErrorHandlerFunc func(*context.Context, error) ) // HandleError fires when a non-nil error returns from a request-scoped dependency at serve-time or the handler itself. -func (fn ErrorHandlerFunc) HandleError(ctx context.Context, err error) { +func (fn ErrorHandlerFunc) HandleError(ctx *context.Context, err error) { fn(ctx, err) } @@ -42,7 +42,7 @@ var ( // DefaultErrorHandler is the default error handler which is fired // when a function returns a non-nil error or a request-scoped dependency failed to binded. - DefaultErrorHandler = ErrorHandlerFunc(func(ctx context.Context, err error) { + DefaultErrorHandler = ErrorHandlerFunc(func(ctx *context.Context, err error) { if err != ErrStopExecution { if status := ctx.GetStatusCode(); status == 0 || !context.StatusCodeNotSuccessful(status) { ctx.StatusCode(DefaultErrStatusCode) @@ -67,7 +67,7 @@ func makeHandler(fn interface{}, c *Container, paramsCount int) context.Handler // 1. A handler which returns just an error, handle it faster. if handlerWithErr, ok := isHandlerWithError(fn); ok { - return func(ctx context.Context) { + return func(ctx *context.Context) { if err := handlerWithErr(ctx); err != nil { c.GetErrorHandler(ctx).HandleError(ctx, err) } @@ -85,7 +85,7 @@ func makeHandler(fn interface{}, c *Container, paramsCount int) context.Handler resultHandler = c.resultHandlers[lidx-i](resultHandler) } - return func(ctx context.Context) { + return func(ctx *context.Context) { inputs := make([]reflect.Value, numIn) for _, binding := range bindings { @@ -131,15 +131,15 @@ func isHandler(fn interface{}) (context.Handler, bool) { return handler, ok } - if handler, ok := fn.(func(context.Context)); ok { + if handler, ok := fn.(func(*context.Context)); ok { return handler, ok } return nil, false } -func isHandlerWithError(fn interface{}) (func(context.Context) error, bool) { - if handlerWithErr, ok := fn.(func(context.Context) error); ok { +func isHandlerWithError(fn interface{}) (func(*context.Context) error, bool) { + if handlerWithErr, ok := fn.(func(*context.Context) error); ok { return handlerWithErr, true } diff --git a/hero/param_test.go b/hero/param_test.go index 88d1d0ab93..baa34d92fa 100644 --- a/hero/param_test.go +++ b/hero/param_test.go @@ -13,7 +13,7 @@ func TestPathParams(t *testing.T) { got = firstname + lastname }) - h.Register(func(ctx context.Context) func() string { return func() string { return "" } }) + h.Register(func(ctx *context.Context) func() string { return func() string { return "" } }) handlerWithOther := h.Handler(func(f func() string, firstname string, lastname string) { got = f() + firstname + lastname }) diff --git a/hero/reflect.go b/hero/reflect.go index 1662afe38f..7865a4aa55 100644 --- a/hero/reflect.go +++ b/hero/reflect.go @@ -58,11 +58,11 @@ func toError(v reflect.Value) error { return v.Interface().(error) } -var contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() +var contextType = reflect.TypeOf((*context.Context)(nil)) // isContext returns true if the "typ" is a type of Context. func isContext(typ reflect.Type) bool { - return typ.Implements(contextTyp) + return typ == contextType } var errorHandlerTyp = reflect.TypeOf((*ErrorHandler)(nil)).Elem() diff --git a/hero/struct.go b/hero/struct.go index b466e5d1aa..2ee16f77f8 100644 --- a/hero/struct.go +++ b/hero/struct.go @@ -92,7 +92,7 @@ func makeStruct(structPtr interface{}, c *Container, partyParamsCount int) *Stru Explicitly(). DestType = typ - newContainer.GetErrorHandler = func(ctx context.Context) ErrorHandler { + newContainer.GetErrorHandler = func(ctx *context.Context) ErrorHandler { if isErrHandler { return ctx.Controller().Interface().(ErrorHandler) } @@ -108,7 +108,7 @@ func makeStruct(structPtr interface{}, c *Container, partyParamsCount int) *Stru // If the dependencies are all static then these are already set-ed at the initialization of this Struct // and the same struct value instance will be returned, ignoring the Context. Otherwise // a new struct value with filled fields by its pre-calculated bindings will be returned instead. -func (s *Struct) Acquire(ctx context.Context) (reflect.Value, error) { +func (s *Struct) Acquire(ctx *context.Context) (reflect.Value, error) { if s.Singleton { ctx.Values().Set(context.ControllerContextKey, s.ptrValue) return s.ptrValue, nil diff --git a/i18n/i18n.go b/i18n/i18n.go index 410a7a5c16..667d5c2ca6 100644 --- a/i18n/i18n.go +++ b/i18n/i18n.go @@ -42,7 +42,7 @@ type I18n struct { // ExtractFunc is the type signature for declaring custom logic // to extract the language tag name. // Defaults to nil. - ExtractFunc func(ctx context.Context) string + ExtractFunc func(ctx *context.Context) string // If not empty, it is language identifier by url query. // // Defaults to "lang". @@ -319,7 +319,7 @@ const acceptLanguageHeaderKey = "Accept-Language" // GetLocale returns the found locale of a request. // It will return the first registered language if nothing else matched. -func (i *I18n) GetLocale(ctx context.Context) context.Locale { +func (i *I18n) GetLocale(ctx *context.Context) context.Locale { var ( index int ok bool @@ -392,7 +392,7 @@ func (i *I18n) GetLocale(ctx context.Context) context.Locale { // GetMessage returns the localized text message for this "r" request based on the key "format". // It returns an empty string if locale or format not found. -func (i *I18n) GetMessage(ctx context.Context, format string, args ...interface{}) string { +func (i *I18n) GetMessage(ctx *context.Context, format string, args ...interface{}) string { loc := i.GetLocale(ctx) if loc != nil { // it's not the default/fallback language and not message found for that lang:key. diff --git a/iris.go b/iris.go index 0aa3f0f58d..94c7f34ec7 100644 --- a/iris.go +++ b/iris.go @@ -1,7 +1,6 @@ package iris import ( - "bytes" stdContext "context" "errors" "fmt" @@ -10,138 +9,38 @@ import ( "net" "net/http" "os" - "path/filepath" "strings" "sync" "time" - "github.com/kataras/iris/v12/cache" "github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/core/errgroup" - "github.com/kataras/iris/v12/core/handlerconv" "github.com/kataras/iris/v12/core/host" "github.com/kataras/iris/v12/core/netutil" "github.com/kataras/iris/v12/core/router" - "github.com/kataras/iris/v12/hero" "github.com/kataras/iris/v12/i18n" requestLogger "github.com/kataras/iris/v12/middleware/logger" "github.com/kataras/iris/v12/middleware/recover" "github.com/kataras/iris/v12/view" "github.com/kataras/golog" - "gopkg.in/yaml.v3" + "github.com/kataras/tunnel" ) // Version is the current version number of the Iris Web Framework. const Version = "12.1.8" -// HTTP status codes as registered with IANA. -// See: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml. -// Raw Copy from the future(tip) net/http std package in order to recude the import path of "net/http" for the users. -const ( - StatusContinue = 100 // RFC 7231, 6.2.1 - StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2 - StatusProcessing = 102 // RFC 2518, 10.1 - StatusEarlyHints = 103 // RFC 8297 - StatusOK = 200 // RFC 7231, 6.3.1 - StatusCreated = 201 // RFC 7231, 6.3.2 - StatusAccepted = 202 // RFC 7231, 6.3.3 - StatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4 - StatusNoContent = 204 // RFC 7231, 6.3.5 - StatusResetContent = 205 // RFC 7231, 6.3.6 - StatusPartialContent = 206 // RFC 7233, 4.1 - StatusMultiStatus = 207 // RFC 4918, 11.1 - StatusAlreadyReported = 208 // RFC 5842, 7.1 - StatusIMUsed = 226 // RFC 3229, 10.4.1 - - StatusMultipleChoices = 300 // RFC 7231, 6.4.1 - StatusMovedPermanently = 301 // RFC 7231, 6.4.2 - StatusFound = 302 // RFC 7231, 6.4.3 - StatusSeeOther = 303 // RFC 7231, 6.4.4 - StatusNotModified = 304 // RFC 7232, 4.1 - StatusUseProxy = 305 // RFC 7231, 6.4.5 - - StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7 - StatusPermanentRedirect = 308 // RFC 7538, 3 - - StatusBadRequest = 400 // RFC 7231, 6.5.1 - StatusUnauthorized = 401 // RFC 7235, 3.1 - StatusPaymentRequired = 402 // RFC 7231, 6.5.2 - StatusForbidden = 403 // RFC 7231, 6.5.3 - StatusNotFound = 404 // RFC 7231, 6.5.4 - StatusMethodNotAllowed = 405 // RFC 7231, 6.5.5 - StatusNotAcceptable = 406 // RFC 7231, 6.5.6 - StatusProxyAuthRequired = 407 // RFC 7235, 3.2 - StatusRequestTimeout = 408 // RFC 7231, 6.5.7 - StatusConflict = 409 // RFC 7231, 6.5.8 - StatusGone = 410 // RFC 7231, 6.5.9 - StatusLengthRequired = 411 // RFC 7231, 6.5.10 - StatusPreconditionFailed = 412 // RFC 7232, 4.2 - StatusRequestEntityTooLarge = 413 // RFC 7231, 6.5.11 - StatusPayloadTooRage = StatusRequestEntityTooLarge - StatusRequestURITooLong = 414 // RFC 7231, 6.5.12 - StatusUnsupportedMediaType = 415 // RFC 7231, 6.5.13 - StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4 - StatusExpectationFailed = 417 // RFC 7231, 6.5.14 - StatusTeapot = 418 // RFC 7168, 2.3.3 - StatusMisdirectedRequest = 421 // RFC 7540, 9.1.2 - StatusUnprocessableEntity = 422 // RFC 4918, 11.2 - StatusLocked = 423 // RFC 4918, 11.3 - StatusFailedDependency = 424 // RFC 4918, 11.4 - StatusTooEarly = 425 // RFC 8470, 5.2. - StatusUpgradeRequired = 426 // RFC 7231, 6.5.15 - StatusPreconditionRequired = 428 // RFC 6585, 3 - StatusTooManyRequests = 429 // RFC 6585, 4 - StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5 - StatusUnavailableForLegalReasons = 451 // RFC 7725, 3 - // Unofficial Client Errors. - StatusPageExpired = context.StatusPageExpired - StatusBlockedByWindowsParentalControls = context.StatusBlockedByWindowsParentalControls - StatusInvalidToken = context.StatusInvalidToken - StatusTokenRequired = context.StatusTokenRequired - // - StatusInternalServerError = 500 // RFC 7231, 6.6.1 - StatusNotImplemented = 501 // RFC 7231, 6.6.2 - StatusBadGateway = 502 // RFC 7231, 6.6.3 - StatusServiceUnavailable = 503 // RFC 7231, 6.6.4 - StatusGatewayTimeout = 504 // RFC 7231, 6.6.5 - StatusHTTPVersionNotSupported = 505 // RFC 7231, 6.6.6 - StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1 - StatusInsufficientStorage = 507 // RFC 4918, 11.5 - StatusLoopDetected = 508 // RFC 5842, 7.2 - StatusNotExtended = 510 // RFC 2774, 7 - StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6 - // Unofficial Server Errors. - StatusBandwidthLimitExceeded = context.StatusBandwidthLimitExceeded - StatusInvalidSSLCertificate = context.StatusInvalidSSLCertificate - StatusSiteOverloaded = context.StatusSiteOverloaded - StatusSiteFrozen = context.StatusSiteFrozen - StatusNetworkReadTimeout = context.StatusNetworkReadTimeout -) - -// StatusText returns a text for the HTTP status code. It returns the empty -// string if the code is unknown. -// -// Shortcut for core/router#StatusText. -var StatusText = context.StatusText - -// HTTP Methods copied from `net/http`. +// Byte unit helpers. const ( - MethodGet = "GET" - MethodPost = "POST" - MethodPut = "PUT" - MethodDelete = "DELETE" - MethodConnect = "CONNECT" - MethodHead = "HEAD" - MethodPatch = "PATCH" - MethodOptions = "OPTIONS" - MethodTrace = "TRACE" + B = 1 << (10 * iota) + KB + MB + GB + TB + PB + EB ) -// MethodNone is an iris-specific "virtual" method -// to store the "offline" routes. -const MethodNone = "NONE" - // Application is responsible to manage the state of the application. // It contains and handles all the necessary parts to create a fast web server. type Application struct { @@ -198,7 +97,7 @@ func New() *Application { Router: router.NewRouter(), } - app.ContextPool = context.New(func() context.Context { + app.ContextPool = context.New(func() interface{} { return context.NewContext(app) }) @@ -212,8 +111,8 @@ func Default() *Application { app := New() app.Use(recover.New()) app.Use(requestLogger.New()) - app.Use(Gzip) - app.Use(GzipReader) + app.Use(Compress) + app.Use(CompressReader) app.defaultMode = true @@ -352,31 +251,6 @@ func (app *Application) Validate(v interface{}) error { return nil } -var ( - // HTML view engine. - // Shortcut of the kataras/iris/view.HTML. - HTML = view.HTML - // Django view engine. - // Shortcut of the kataras/iris/view.Django. - Django = view.Django - // Handlebars view engine. - // Shortcut of the kataras/iris/view.Handlebars. - Handlebars = view.Handlebars - // Pug view engine. - // Shortcut of the kataras/iris/view.Pug. - Pug = view.Pug - // Amber view engine. - // Shortcut of the kataras/iris/view.Amber. - Amber = view.Amber - // Jet view engine. - // Shortcut of the kataras/iris/view.Jet. - Jet = view.Jet -) - -// NoLayout to disable layout for a particular template file -// A shortcut for the `view#NoLayout`. -const NoLayout = view.NoLayout - // RegisterView should be used to register view engines mapping to a root directory // and the template file(s) extension. func (app *Application) RegisterView(viewEngine view.Engine) { @@ -406,271 +280,6 @@ func (app *Application) View(writer io.Writer, filename string, layout string, b return err } -var ( - // LimitRequestBodySize is a middleware which sets a request body size limit - // for all next handlers in the chain. - // - // A shortcut for the `context#LimitRequestBodySize`. - LimitRequestBodySize = context.LimitRequestBodySize - // NewConditionalHandler returns a single Handler which can be registered - // as a middleware. - // Filter is just a type of Handler which returns a boolean. - // Handlers here should act like middleware, they should contain `ctx.Next` to proceed - // to the next handler of the chain. Those "handlers" are registered to the per-request context. - // - // - // It checks the "filter" and if passed then - // it, correctly, executes the "handlers". - // - // If passed, this function makes sure that the Context's information - // about its per-request handler chain based on the new "handlers" is always updated. - // - // If not passed, then simply the Next handler(if any) is executed and "handlers" are ignored. - // Example can be found at: _examples/routing/conditional-chain. - // - // A shortcut for the `context#NewConditionalHandler`. - NewConditionalHandler = context.NewConditionalHandler - // FileServer returns a Handler which serves files from a specific system, phyisical, directory - // or an embedded one. - // The first parameter is the directory, relative to the executable program. - // The second optional parameter is any optional settings that the caller can use. - // - // See `Party#HandleDir` too. - // Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server - // A shortcut for the `router.FileServer`. - FileServer = router.FileServer - // DirListRich can be passed to `DirOptions.DirList` field - // to override the default file listing appearance. - // Read more at: `core/router.DirListRich`. - DirListRich = router.DirListRich - // StripPrefix returns a handler that serves HTTP requests - // by removing the given prefix from the request URL's Path - // and invoking the handler h. StripPrefix handles a - // request for a path that doesn't begin with prefix by - // replying with an HTTP 404 not found error. - // - // Usage: - // fileserver := iris.FileServer("./static_files", DirOptions {...}) - // h := iris.StripPrefix("/static", fileserver) - // app.Get("/static/{file:path}", h) - // app.Head("/static/{file:path}", h) - StripPrefix = router.StripPrefix - // Gzip is a middleware which enables writing - // using gzip compression, if client supports. - // - // A shortcut for the `context#Gzip`. - Gzip = context.Gzip - // GzipReader is a middleware which enables gzip decompression, - // when client sends gzip compressed data. - // - // Similar to: func(ctx iris.Context) { - // ctx.GzipReader(true) - // ctx.Next() - // } - // - // A shortcut for the `context#GzipReader`. - GzipReader = context.GzipReader - // FromStd converts native http.Handler, http.HandlerFunc & func(w, r, next) to context.Handler. - // - // Supported form types: - // .FromStd(h http.Handler) - // .FromStd(func(w http.ResponseWriter, r *http.Request)) - // .FromStd(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)) - // - // A shortcut for the `handlerconv#FromStd`. - FromStd = handlerconv.FromStd - // Cache is a middleware providing server-side cache functionalities - // to the next handlers, can be used as: `app.Get("/", iris.Cache, aboutHandler)`. - // It should be used after Static methods. - // See `iris#Cache304` for an alternative, faster way. - // - // Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/#caching - Cache = cache.Handler - // NoCache is a middleware which overrides the Cache-Control, Pragma and Expires headers - // in order to disable the cache during the browser's back and forward feature. - // - // A good use of this middleware is on HTML routes; to refresh the page even on "back" and "forward" browser's arrow buttons. - // - // See `iris#StaticCache` for the opposite behavior. - // - // A shortcut of the `cache#NoCache` - NoCache = cache.NoCache - // StaticCache middleware for caching static files by sending the "Cache-Control" and "Expires" headers to the client. - // It accepts a single input parameter, the "cacheDur", a time.Duration that it's used to calculate the expiration. - // - // If "cacheDur" <=0 then it returns the `NoCache` middleware instaed to disable the caching between browser's "back" and "forward" actions. - // - // Usage: `app.Use(iris.StaticCache(24 * time.Hour))` or `app.Use(iris.StaticCache(-1))`. - // A middleware, which is a simple Handler can be called inside another handler as well, example: - // cacheMiddleware := iris.StaticCache(...) - // func(ctx iris.Context){ - // cacheMiddleware(ctx) - // [...] - // } - // - // A shortcut of the `cache#StaticCache` - StaticCache = cache.StaticCache - // Cache304 sends a `StatusNotModified` (304) whenever - // the "If-Modified-Since" request header (time) is before the - // time.Now() + expiresEvery (always compared to their UTC values). - // Use this, which is a shortcut of the, `chache#Cache304` instead of the "github.com/kataras/iris/v12/cache" or iris.Cache - // for better performance. - // Clients that are compatible with the http RCF (all browsers are and tools like postman) - // will handle the caching. - // The only disadvantage of using that instead of server-side caching - // is that this method will send a 304 status code instead of 200, - // So, if you use it side by side with other micro services - // you have to check for that status code as well for a valid response. - // - // Developers are free to extend this method's behavior - // by watching system directories changes manually and use of the `ctx.WriteWithExpiration` - // with a "modtime" based on the file modified date, - // similar to the `HandleDir`(which sends status OK(200) and browser disk caching instead of 304). - // - // A shortcut of the `cache#Cache304`. - Cache304 = cache.Cache304 - - // CookieAllowReclaim accepts the Context itself. - // If set it will add the cookie to (on `CookieSet`, `CookieSetKV`, `CookieUpsert`) - // or remove the cookie from (on `CookieRemove`) the Request object too. - // - // A shortcut for the `context#CookieAllowReclaim`. - CookieAllowReclaim = context.CookieAllowReclaim - // CookieAllowSubdomains set to the Cookie Options - // in order to allow subdomains to have access to the cookies. - // It sets the cookie's Domain field (if was empty) and - // it also sets the cookie's SameSite to lax mode too. - // - // A shortcut for the `context#CookieAllowSubdomains`. - CookieAllowSubdomains = context.CookieAllowSubdomains - // CookieSameSite sets a same-site rule for cookies to set. - // SameSite allows a server to define a cookie attribute making it impossible for - // the browser to send this cookie along with cross-site requests. The main - // goal is to mitigate the risk of cross-origin information leakage, and provide - // some protection against cross-site request forgery attacks. - // - // See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details. - // - // A shortcut for the `context#CookieSameSite`. - CookieSameSite = context.CookieHTTPOnly - // CookieSecure sets the cookie's Secure option if the current request's - // connection is using TLS. See `CookieHTTPOnly` too. - // - // A shortcut for the `context#CookieSecure`. - CookieSecure = context.CookieSecure - // CookieHTTPOnly is a `CookieOption`. - // Use it to set the cookie's HttpOnly field to false or true. - // HttpOnly field defaults to true for `RemoveCookie` and `SetCookieKV`. - // - // A shortcut for the `context#CookieHTTPOnly`. - CookieHTTPOnly = context.CookieHTTPOnly - // CookiePath is a `CookieOption`. - // Use it to change the cookie's Path field. - // - // A shortcut for the `context#CookiePath`. - CookiePath = context.CookiePath - // CookieCleanPath is a `CookieOption`. - // Use it to clear the cookie's Path field, exactly the same as `CookiePath("")`. - // - // A shortcut for the `context#CookieCleanPath`. - CookieCleanPath = context.CookieCleanPath - // CookieExpires is a `CookieOption`. - // Use it to change the cookie's Expires and MaxAge fields by passing the lifetime of the cookie. - // - // A shortcut for the `context#CookieExpires`. - CookieExpires = context.CookieExpires - // CookieEncoding accepts a value which implements `Encode` and `Decode` methods. - // It calls its `Encode` on `Context.SetCookie, UpsertCookie, and SetCookieKV` methods. - // And on `Context.GetCookie` method it calls its `Decode`. - // - // A shortcut for the `context#CookieEncoding`. - CookieEncoding = context.CookieEncoding - - // IsErrPath can be used at `context#ReadForm` and `context#ReadQuery`. - // It reports whether the incoming error is type of `formbinder.ErrPath`, - // which can be ignored when server allows unknown post values to be sent by the client. - // - // A shortcut for the `context#IsErrPath`. - IsErrPath = context.IsErrPath - // ErrEmptyForm is the type error which API users can make use of - // to check if a form was empty on `Context.ReadForm`. - // - // A shortcut for the `context#ErrEmptyForm`. - ErrEmptyForm = context.ErrEmptyForm - // NewProblem returns a new Problem. - // Head over to the `Problem` type godoc for more. - // - // A shortcut for the `context#NewProblem`. - NewProblem = context.NewProblem - // XMLMap wraps a map[string]interface{} to compatible xml marshaler, - // in order to be able to render maps as XML on the `Context.XML` method. - // - // Example: `Context.XML(XMLMap("Root", map[string]interface{}{...})`. - // - // A shortcut for the `context#XMLMap`. - XMLMap = context.XMLMap - // ErrStopExecution if returned from a hero middleware or a request-scope dependency - // stops the handler's execution, see _examples/dependency-injection/basic/middleware. - ErrStopExecution = hero.ErrStopExecution - // ErrHijackNotSupported is returned by the Hijack method to - // indicate that Hijack feature is not available. - // - // A shortcut for the `context#ErrHijackNotSupported`. - ErrHijackNotSupported = context.ErrHijackNotSupported - // ErrPushNotSupported is returned by the Push method to - // indicate that HTTP/2 Push support is not available. - // - // A shortcut for the `context#ErrPushNotSupported`. - ErrPushNotSupported = context.ErrPushNotSupported - // ErrGzipNotSupported may be returned from - // `WriteGzip` and `GzipReader` methods if - // the client does not support the "gzip" compression. - // - // A shortcut for the `context#ErrGzipNotSupported`. - ErrGzipNotSupported = context.ErrGzipNotSupported -) - -// Constants for input argument at `router.RouteRegisterRule`. -// See `Party#SetRegisterRule`. -const ( - // RouteOverride replaces an existing route with the new one, the default rule. - RouteOverride = router.RouteOverride - // RouteSkip keeps the original route and skips the new one. - RouteSkip = router.RouteSkip - // RouteError log when a route already exists, shown after the `Build` state, - // server never starts. - RouteError = router.RouteError - // RouteOverlap will overlap the new route to the previous one. - // If the route stopped and its response can be reset then the new route will be execute. - RouteOverlap = router.RouteOverlap -) - -// Contains the enum values of the `Context.GetReferrer()` method, -// shortcuts of the context subpackage. -const ( - ReferrerInvalid = context.ReferrerInvalid - ReferrerIndirect = context.ReferrerIndirect - ReferrerDirect = context.ReferrerDirect - ReferrerEmail = context.ReferrerEmail - ReferrerSearch = context.ReferrerSearch - ReferrerSocial = context.ReferrerSocial - - ReferrerNotGoogleSearch = context.ReferrerNotGoogleSearch - ReferrerGoogleOrganicSearch = context.ReferrerGoogleOrganicSearch - ReferrerGoogleAdwords = context.ReferrerGoogleAdwords -) - -// Byte unit helpers. -const ( - B = 1 << (10 * iota) - KB - MB - GB - TB - PB - EB -) - // ConfigureHost accepts one or more `host#Configuration`, these configurators functions // can access the host created by `app.Run`, // they're being executed when application is ready to being served to the public. @@ -766,11 +375,6 @@ func (app *Application) NewHost(srv *http.Server) *host.Supervisor { return su } -// RegisterOnInterrupt registers a global function to call when CTRL+C/CMD+C pressed or a unix kill command received. -// -// A shortcut for the `host#RegisterOnInterrupt`. -var RegisterOnInterrupt = host.RegisterOnInterrupt - // Shutdown gracefully terminates all the application's server hosts and any tunnels. // Returns an error on the first failure, otherwise nil. func (app *Application) Shutdown(ctx stdContext.Context) error { @@ -790,7 +394,7 @@ func (app *Application) Shutdown(ctx stdContext.Context) error { continue } - if err := app.config.Tunneling.stopTunnel(t); err != nil { + if err := app.config.Tunneling.StopTunnel(t); err != nil { return err } } @@ -880,7 +484,7 @@ func (app *Application) Build() error { if !app.Router.Downgraded() { // router - if err := app.tryInjectLiveReload(); err != nil { + if _, err := injectLiveReload(app.ContextPool, app.Router); err != nil { rp.Errf("LiveReload: init: failed: %v", err) } @@ -1139,141 +743,26 @@ func (app *Application) Run(serve Runner, withOrWithout ...Configurator) error { return err } -// tryInjectLiveReload tries to check if this application -// runs under https://github.com/kataras/iris-cli and if so -// then it checks if the livereload is enabled and then injects -// the watch listener (js script) on every HTML response. -// It has a slight performance cost but -// this (iris-cli with watch and livereload enabled) -// is meant to be used only in development mode. -// It does a full reload at the moment and if the port changed -// at runtime it will fire 404 instead of redirecting to the correct port (that's a TODO). -// -// tryInjectLiveReload runs right before Build -> BuildRouter. -func (app *Application) tryInjectLiveReload() error { - conf := struct { - Running bool `yaml:"Running,omitempty"` - LiveReload struct { - Disable bool `yaml:"Disable"` - Port int `yaml:"Port"` - } `yaml:"LiveReload"` - }{} - // defaults to disabled here. - conf.LiveReload.Disable = true - - wd, err := os.Getwd() - if err != nil { - return err - } - - for _, path := range []string{".iris.yml" /*, "../.iris.yml", "../../.iris.yml" */} { - path = filepath.Join(wd, path) - - if _, err := os.Stat(path); err == nil { - inFile, err := os.OpenFile(path, os.O_RDONLY, 0644) - if err != nil { - return err - } - - dec := yaml.NewDecoder(inFile) - err = dec.Decode(&conf) - inFile.Close() - if err != nil { - return err - } - - break - } - } - - if !conf.Running || conf.LiveReload.Disable { - return nil - } - - scriptReloadJS := []byte(fmt.Sprintf(``, conf.LiveReload.Port)) - - bodyCloseTag := []byte("") - - app.Router.WrapRouter(func(w http.ResponseWriter, r *http.Request, _ http.HandlerFunc) { - ctx := app.ContextPool.Acquire(w, r) - rec := ctx.Recorder() // Record everything and write all in once at the Context release. - app.ServeHTTPC(ctx) // We directly call request handler with Context. - - if strings.HasPrefix(ctx.GetContentType(), "text/html") { - // delete(rec.Header(), context.ContentLengthHeaderKey) - - body := rec.Body() - - if idx := bytes.LastIndex(body, bodyCloseTag); idx > 0 { - // add the script right before last . - body = append(body[:idx], bytes.Replace(body[idx:], bodyCloseTag, append(scriptReloadJS, bodyCloseTag...), 1)...) - rec.SetBody(body) - } else { - // Just append it. - rec.Write(scriptReloadJS) // nolint:errcheck - } - - if _, has := rec.Header()[context.ContentLengthHeaderKey]; has { - rec.Header().Set(context.ContentLengthHeaderKey, fmt.Sprintf("%d", len(rec.Body()))) - } - } - - app.ContextPool.Release(ctx) - }) - - return nil -} - // https://ngrok.com/docs func (app *Application) tryStartTunneling() { - if !app.config.Tunneling.isEnabled() { + if len(app.config.Tunneling.Tunnels) == 0 { return } app.ConfigureHost(func(su *host.Supervisor) { su.RegisterOnServe(func(h host.TaskHost) { - tc := app.config.Tunneling - if tc.WebInterface == "" { - tc.WebInterface = "http://127.0.0.1:4040" + publicAddrs, err := tunnel.Start(app.config.Tunneling) + if err != nil { + app.logger.Errorf("Host: tunneling error: %v", err) + return } - for tunnIdx, t := range tc.Tunnels { - if t.Name == "" { - t.Name = fmt.Sprintf("iris-app-%d-%s", tunnIdx+1, time.Now().Format(app.config.TimeFormat)) - } - - if t.Addr == "" { - t.Addr = su.Server.Addr - } + publicAddr := publicAddrs[0] + // to make subdomains resolution still based on this new remote, public addresses. + app.config.vhost = publicAddr[strings.Index(publicAddr, "://")+3:] - var publicAddr string - err := tc.startTunnel(t, &publicAddr) - if err != nil { - app.logger.Errorf("Host: tunneling error: %v", err) - return - } - - // to make subdomains resolution still based on this new remote, public addresses. - app.config.vhost = publicAddr[strings.Index(publicAddr, "://")+3:] - - directLog := []byte(fmt.Sprintf("• Public Address: %s\n", publicAddr)) - app.logger.Printer.Write(directLog) // nolint:errcheck - } + directLog := []byte(fmt.Sprintf("• Public Address: %s\n", publicAddr)) + app.logger.Printer.Write(directLog) // nolint:errcheck }) }) } diff --git a/macro/handler/handler.go b/macro/handler/handler.go index f41a17b0f5..42016cada2 100644 --- a/macro/handler/handler.go +++ b/macro/handler/handler.go @@ -37,7 +37,7 @@ func CanMakeHandler(tmpl macro.Template) (needsMacroHandler bool) { func MakeHandler(tmpl macro.Template) context.Handler { filter := MakeFilter(tmpl) - return func(ctx context.Context) { + return func(ctx *context.Context) { if !filter(ctx) { if ctx.GetCurrentRoute().StatusErrorCode() == ctx.GetStatusCode() { ctx.Next() @@ -61,7 +61,7 @@ func MakeFilter(tmpl macro.Template) context.Filter { return nil } - return func(ctx context.Context) bool { + return func(ctx *context.Context) bool { for _, p := range tmpl.Params { if !p.CanEval() { continue // allow. diff --git a/middleware/basicauth/basicauth.go b/middleware/basicauth/basicauth.go index 4a09e9d2aa..79831dfb72 100644 --- a/middleware/basicauth/basicauth.go +++ b/middleware/basicauth/basicauth.go @@ -97,7 +97,7 @@ func (b *basicAuthMiddleware) findAuth(headerValue string) (*encodedUser, bool) return nil, false } -func (b *basicAuthMiddleware) askForCredentials(ctx context.Context) { +func (b *basicAuthMiddleware) askForCredentials(ctx *context.Context) { ctx.Header("WWW-Authenticate", b.realmHeaderValue) ctx.StatusCode(iris.StatusUnauthorized) if b.askHandlerEnabled { @@ -106,7 +106,7 @@ func (b *basicAuthMiddleware) askForCredentials(ctx context.Context) { } // Serve the actual middleware -func (b *basicAuthMiddleware) Serve(ctx context.Context) { +func (b *basicAuthMiddleware) Serve(ctx *context.Context) { auth, found := b.findAuth(ctx.GetHeader("Authorization")) if !found { b.askForCredentials(ctx) diff --git a/middleware/basicauth/config.go b/middleware/basicauth/config.go index ef1e5ecaa9..3b885b14f0 100644 --- a/middleware/basicauth/config.go +++ b/middleware/basicauth/config.go @@ -50,6 +50,6 @@ func DefaultConfig() Config { } // User returns the user from context key same as ctx.Request().BasicAuth(). -func (c Config) User(ctx context.Context) (string, string, bool) { +func (c Config) User(ctx *context.Context) (string, string, bool) { return ctx.Request().BasicAuth() } diff --git a/middleware/hcaptcha/hcaptcha.go b/middleware/hcaptcha/hcaptcha.go index 741772f1ea..e6cb41a360 100644 --- a/middleware/hcaptcha/hcaptcha.go +++ b/middleware/hcaptcha/hcaptcha.go @@ -19,7 +19,7 @@ var ( ResponseContextKey string = "iris.hcaptcha" // DefaultFailureHandler is the default HTTP handler that is fired on hcaptcha failures. // See `Client.FailureHandler`. - DefaultFailureHandler = func(ctx context.Context) { + DefaultFailureHandler = func(ctx *context.Context) { ctx.StopWithStatus(http.StatusTooManyRequests) } ) @@ -73,7 +73,7 @@ func New(secret string, options ...Option) context.Handler { // otherwise it calls the Client's `FailureHandler`. // The hcaptcha's `Response` (which contains any `ErrorCodes`) // is saved on the Request's Context (see `GetResponseFromContext`). -func (c *Client) Handler(ctx context.Context) { +func (c *Client) Handler(ctx *context.Context) { v := SiteVerify(ctx, c.secret) ctx.Values().Set(ResponseContextKey, v) if v.Success { @@ -93,7 +93,7 @@ const apiURL = "https://hcaptcha.com/siteverify" // It returns the hcaptcha's `Response`. // The `response.Success` reports whether the validation passed. // Any errors are passed through the `response.ErrorCodes` field. -func SiteVerify(ctx context.Context, secret string) (response Response) { +func SiteVerify(ctx *context.Context, secret string) (response Response) { generatedResponseID := ctx.FormValue("h-captcha-response") if generatedResponseID == "" { @@ -130,7 +130,7 @@ func SiteVerify(ctx context.Context, secret string) (response Response) { } // Get returns the hcaptcha `Response` of the current request and reports whether was found or not. -func Get(ctx context.Context) (Response, bool) { +func Get(ctx *context.Context) (Response, bool) { v := ctx.Values().Get(ResponseContextKey) if v != nil { if response, ok := v.(Response); ok { @@ -161,6 +161,6 @@ func ParseForm(dataSiteKey, postActionRelativePath string) string { // RenderForm writes the `HTMLForm` to "w" response writer. // See `_examples/auth/hcaptcha/templates/register_form.html` example for a custom form instead. -func RenderForm(ctx context.Context, dataSiteKey, postActionRelativePath string) (int, error) { +func RenderForm(ctx *context.Context, dataSiteKey, postActionRelativePath string) (int, error) { return ctx.HTML(ParseForm(dataSiteKey, postActionRelativePath)) } diff --git a/middleware/jwt/jwt.go b/middleware/jwt/jwt.go index a33dbfeac7..00df66633e 100644 --- a/middleware/jwt/jwt.go +++ b/middleware/jwt/jwt.go @@ -21,12 +21,12 @@ func init() { // TokenExtractor is a function that takes a context as input and returns // a token. An empty string should be returned if no token found // without additional information. -type TokenExtractor func(context.Context) string +type TokenExtractor func(*context.Context) string // FromHeader is a token extractor. // It reads the token from the Authorization request header of form: // Authorization: "Bearer {token}". -func FromHeader(ctx context.Context) string { +func FromHeader(ctx *context.Context) string { authHeader := ctx.GetHeader("Authorization") if authHeader == "" { return "" @@ -43,7 +43,7 @@ func FromHeader(ctx context.Context) string { // FromQuery is a token extractor. // It reads the token from the "token" url query parameter. -func FromQuery(ctx context.Context) string { +func FromQuery(ctx *context.Context) string { return ctx.URLParam("token") } @@ -52,7 +52,7 @@ func FromQuery(ctx context.Context) string { // The request content-type should contain the: application/json header value, otherwise // this method will not try to read and consume the body. func FromJSON(jsonKey string) TokenExtractor { - return func(ctx context.Context) string { + return func(ctx *context.Context) string { if ctx.GetContentTypeRequested() != context.ContentJSONHeaderValue { return "" } @@ -388,7 +388,7 @@ func validateMapClaims(m map[string]interface{}, e jwt.Expected, leeway time.Dur // a new token to the client in plain text format. // // Use the `Token` method to get a new generated token raw string value. -func (j *JWT) WriteToken(ctx context.Context, claims interface{}) error { +func (j *JWT) WriteToken(ctx *context.Context, claims interface{}) error { token, err := j.Token(claims) if err != nil { ctx.StatusCode(500) @@ -418,18 +418,18 @@ type ( Validate() error } claimsContextValidator interface { - Validate(ctx context.Context) error + Validate(ctx *context.Context) error } ) // IsValidated reports whether a token is already validated through // `VerifyToken`. It returns true when the claims are compatible // validators: a `Claims` value or a value that implements the `Validate() error` method. -func IsValidated(ctx context.Context) bool { // see the `ReadClaims`. +func IsValidated(ctx *context.Context) bool { // see the `ReadClaims`. return ctx.Values().Get(needsValidationContextKey) == nil } -func validateClaims(ctx context.Context, claims interface{}) (err error) { +func validateClaims(ctx *context.Context, claims interface{}) (err error) { switch c := claims.(type) { case claimsValidator: err = c.ValidateWithLeeway(jwt.Expected{Time: time.Now()}, 0) @@ -467,7 +467,7 @@ func validateClaims(ctx context.Context, claims interface{}) (err error) { // VerifyToken verifies (and decrypts) the request token, // it also validates and binds the parsed token's claims to the "claimsPtr" (destination). // It does return a nil error on success. -func (j *JWT) VerifyToken(ctx context.Context, claimsPtr interface{}) error { +func (j *JWT) VerifyToken(ctx *context.Context, claimsPtr interface{}) error { var token string for _, extract := range j.Extractors { @@ -520,7 +520,7 @@ const ( // // A call of `ReadClaims` is required to validate and acquire the jwt claims // on the next request. -func (j *JWT) Verify(ctx context.Context) { +func (j *JWT) Verify(ctx *context.Context) { var raw json.RawMessage if err := j.VerifyToken(ctx, &raw); err != nil { ctx.StopWithStatus(401) @@ -534,7 +534,7 @@ func (j *JWT) Verify(ctx context.Context) { // ReadClaims binds the "claimsPtr" (destination) // to the verified (and decrypted) claims. // The `Verify` method should be called first (registered as middleware). -func ReadClaims(ctx context.Context, claimsPtr interface{}) error { +func ReadClaims(ctx *context.Context, claimsPtr interface{}) error { v := ctx.Values().Get(ClaimsContextKey) if v == nil { return ErrMissing @@ -589,7 +589,7 @@ func ReadClaims(ctx context.Context, claimsPtr interface{}) error { // } // [use claims...] // }) -func Get(ctx context.Context) (interface{}, error) { +func Get(ctx *context.Context) (interface{}, error) { claims := ctx.Values().Get(ClaimsContextKey) if claims == nil { return nil, ErrMissing diff --git a/middleware/logger/config.go b/middleware/logger/config.go index 7c928c8d3b..cf6ef60173 100644 --- a/middleware/logger/config.go +++ b/middleware/logger/config.go @@ -8,7 +8,7 @@ import ( // The SkipperFunc signature, used to serve the main request without logs. // See `Configuration` too. -type SkipperFunc func(ctx context.Context) bool +type SkipperFunc func(ctx *context.Context) bool // Config contains the options for the logger middleware // can be optionally be passed to the `New`. @@ -71,7 +71,7 @@ type Config struct { LogFunc func(endTime time.Time, latency time.Duration, status, ip, method, path string, message interface{}, headerMessage interface{}) // LogFuncCtx can be used instead of `LogFunc` if handlers need to customize the output based on // custom request-time information that the LogFunc isn't aware of. - LogFuncCtx func(ctx context.Context, latency time.Duration) + LogFuncCtx func(ctx *context.Context, latency time.Duration) // Skippers used to skip the logging i.e by `ctx.Path()` and serve // the next/main handler immediately. Skippers []SkipperFunc @@ -110,7 +110,7 @@ func (c *Config) buildSkipper() { return } skippersLocked := c.Skippers[0:] - c.skip = func(ctx context.Context) bool { + c.skip = func(ctx *context.Context) bool { for _, s := range skippersLocked { if s(ctx) { return true diff --git a/middleware/logger/logger.go b/middleware/logger/logger.go index 8493b0c18e..af61b639be 100644 --- a/middleware/logger/logger.go +++ b/middleware/logger/logger.go @@ -36,7 +36,7 @@ func New(cfg ...Config) context.Handler { } // Serve serves the middleware -func (l *requestLoggerMiddleware) ServeHTTP(ctx context.Context) { +func (l *requestLoggerMiddleware) ServeHTTP(ctx *context.Context) { // skip logs and serve the main request immediately if l.config.skip != nil { if l.config.skip(ctx) { diff --git a/middleware/pprof/pprof.go b/middleware/pprof/pprof.go index 3b46f59796..a71b2b1004 100644 --- a/middleware/pprof/pprof.go +++ b/middleware/pprof/pprof.go @@ -26,7 +26,7 @@ func New() context.Handler { threadcreateHandler := handlerconv.FromStd(pprof.Handler("threadcreate")) debugBlockHandler := handlerconv.FromStd(pprof.Handler("block")) - return func(ctx context.Context) { + return func(ctx *context.Context) { ctx.ContentType("text/html") action := ctx.Params().Get("action") if action != "" { diff --git a/middleware/rate/rate.go b/middleware/rate/rate.go index 1827c6e4d0..1dedb8f0f5 100644 --- a/middleware/rate/rate.go +++ b/middleware/rate/rate.go @@ -35,7 +35,7 @@ func ExceedHandler(handler context.Handler) Option { // ClientData is an `Option` that can be passed at the `Limit` package-level function. // It accepts a function which provides the Iris Context and should return custom data // that will be stored to the Client and be retrieved as `Get(ctx).Client.Data` later on. -func ClientData(clientDataFunc func(ctx context.Context) interface{}) Option { +func ClientData(clientDataFunc func(ctx *context.Context) interface{}) Option { return func(l *Limiter) { l.clientDataFunc = clientDataFunc } @@ -79,8 +79,8 @@ type ( // old clients from the memory. Limiter is not exposed by a function, // callers should use it inside an `Option` for the `Limit` package-level function. Limiter struct { - clientDataFunc func(ctx context.Context) interface{} // fill the Client's Data field. - exceedHandler context.Handler // when too many requests. + clientDataFunc func(ctx *context.Context) interface{} // fill the Client's Data field. + exceedHandler context.Handler // when too many requests. limit rate.Limit burstSize int @@ -116,7 +116,7 @@ func Limit(limit float64, burst int, options ...Option) context.Handler { clients: make(map[string]*Client), limit: rate.Limit(limit), burstSize: burst, - exceedHandler: func(ctx context.Context) { + exceedHandler: func(ctx *context.Context) { ctx.StopWithStatus(429) // Too Many Requests. }, } @@ -139,7 +139,7 @@ func (l *Limiter) Purge(condition func(*Client) bool) { l.mu.Unlock() } -func (l *Limiter) serveHTTP(ctx context.Context) { +func (l *Limiter) serveHTTP(ctx *context.Context) { id := getIdentifier(ctx) l.mu.RLock() client, ok := l.clients[id] @@ -182,11 +182,11 @@ const identifierContextKey = "iris.ratelimit.identifier" // SetIdentifier can be called manually from a handler or a middleare // to change the identifier per client. The default key for a client is its Remote IP. -func SetIdentifier(ctx context.Context, key string) { +func SetIdentifier(ctx *context.Context, key string) { ctx.Values().Set(identifierContextKey, key) } -func getIdentifier(ctx context.Context) string { +func getIdentifier(ctx *context.Context) string { if entry, ok := ctx.Values().GetEntry(identifierContextKey); ok { return entry.ValueRaw.(string) } @@ -202,7 +202,7 @@ const clientContextKey = "iris.ratelimit.client" // You can read more about X-RateLimit response headers at: // https://tools.ietf.org/id/draft-polli-ratelimit-headers-00.html. // A good example of that is the GitHub API itself: https://developer.github.com/v3/#rate-limiting -func Get(ctx context.Context) *Client { +func Get(ctx *context.Context) *Client { if v := ctx.Values().Get(clientContextKey); v != nil { if c, ok := v.(*Client); ok { return c diff --git a/middleware/recaptcha/recaptcha.go b/middleware/recaptcha/recaptcha.go index 29eb7b9a7b..291e099540 100644 --- a/middleware/recaptcha/recaptcha.go +++ b/middleware/recaptcha/recaptcha.go @@ -42,7 +42,7 @@ var Client = netutil.Client(time.Duration(20 * time.Second)) // // Use `SiteVerify` to verify a request inside another handler if needed. func New(secret string) context.Handler { - return func(ctx context.Context) { + return func(ctx *context.Context) { if SiteVerify(ctx, secret).Success { ctx.Next() } @@ -54,7 +54,7 @@ func New(secret string) context.Handler { // then validation passed. // // Use `New` for middleware use instead. -func SiteVerify(ctx context.Context, secret string) (response Response) { +func SiteVerify(ctx *context.Context, secret string) (response Response) { generatedResponseID := ctx.FormValue("g-recaptcha-response") if generatedResponseID == "" { response.ErrorCodes = append(response.ErrorCodes, @@ -113,7 +113,7 @@ var recaptchaForm = `
// Example Code: // // Method: "POST" | Path: "/contact" -// func postContact(ctx context.Context) { +// func postContact(ctx *context.Context) { // // [...] // response := recaptcha.SiteVerify(ctx, recaptchaSecret) // @@ -125,7 +125,7 @@ var recaptchaForm = ` // } // // Method: "GET" | Path: "/contact" -// func getContact(ctx context.Context) { +// func getContact(ctx *context.Context) { // // render the recaptcha form // ctx.HTML(recaptcha.GetFormHTML(recaptchaPublic, "/contact")) // } diff --git a/middleware/recover/recover.go b/middleware/recover/recover.go index bceda57876..9208093fba 100644 --- a/middleware/recover/recover.go +++ b/middleware/recover/recover.go @@ -13,7 +13,7 @@ func init() { context.SetHandlerName("iris/middleware/recover.*", "iris.recover") } -func getRequestLogs(ctx context.Context) string { +func getRequestLogs(ctx *context.Context) string { var status, ip, method, path string status = strconv.Itoa(ctx.GetStatusCode()) path = ctx.Path() @@ -27,7 +27,7 @@ func getRequestLogs(ctx context.Context) string { // it recovers from panics and logs // the panic message to the application's logger "Warn" level. func New() context.Handler { - return func(ctx context.Context) { + return func(ctx *context.Context) { defer func() { if err := recover(); err != nil { if ctx.IsStopped() { diff --git a/middleware/requestid/requestid.go b/middleware/requestid/requestid.go index 04d1a30d96..103a68a36c 100644 --- a/middleware/requestid/requestid.go +++ b/middleware/requestid/requestid.go @@ -14,7 +14,7 @@ const xRequestIDHeaderValue = "X-Request-Id" // Generator defines the function which should extract or generate // a Request ID. See `DefaultGenerator` and `New` package-level functions. -type Generator func(ctx context.Context) string +type Generator func(ctx *context.Context) string // DefaultGenerator is the default `Generator` that is used // when nil is passed on `New` package-level function. @@ -22,7 +22,7 @@ type Generator func(ctx context.Context) string // or, if missing, it generates a new UUID(v4) and sets the header and context value. // // See `Get` package-level function too. -var DefaultGenerator Generator = func(ctx context.Context) string { +var DefaultGenerator Generator = func(ctx *context.Context) string { id := ctx.GetHeader(xRequestIDHeaderValue) if id == "" { @@ -50,7 +50,7 @@ func New(generator ...Generator) context.Handler { gen = generator[0] } - return func(ctx context.Context) { + return func(ctx *context.Context) { if Get(ctx) != "" { ctx.Next() return @@ -71,7 +71,7 @@ func New(generator ...Generator) context.Handler { // Get returns the Request ID or empty string. // // A shortcut of `context.GetID().(string)`. -func Get(ctx context.Context) string { +func Get(ctx *context.Context) string { v := ctx.GetID() if v != nil { if id, ok := v.(string); ok { diff --git a/middleware/requestid/requestid_test.go b/middleware/requestid/requestid_test.go index 755cab116f..a5478d4b24 100644 --- a/middleware/requestid/requestid_test.go +++ b/middleware/requestid/requestid_test.go @@ -24,7 +24,7 @@ func TestRequestID(t *testing.T) { const expectedCustomID = "my_id" custom := app.Party("/custom") { - customGen := func(ctx context.Context) string { + customGen := func(ctx *context.Context) string { return expectedCustomID } @@ -35,7 +35,7 @@ func TestRequestID(t *testing.T) { const expectedErrMsg = "no id" customWithErr := app.Party("/custom_err") { - customGen := func(ctx context.Context) string { + customGen := func(ctx *context.Context) string { ctx.StopWithText(iris.StatusUnauthorized, expectedErrMsg) return "" } diff --git a/mvc/controller.go b/mvc/controller.go index 2cb55a02cd..963ddc41ae 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -15,8 +15,8 @@ import ( // completed by the end controller then the BeginRequest and EndRequest // are called between the controller's method responsible for the incoming request. type BaseController interface { - BeginRequest(context.Context) - EndRequest(context.Context) + BeginRequest(*context.Context) + EndRequest(*context.Context) } type shared interface { @@ -400,7 +400,7 @@ func (c *ControllerActivator) handlerOf(relPath, methodName string) context.Hand handler := c.injector.MethodHandler(methodName, paramsCount) if isBaseController(c.Type) { - return func(ctx context.Context) { + return func(ctx *context.Context) { ctrl, err := c.injector.Acquire(ctx) if err != nil { // if err != hero.ErrStopExecution { diff --git a/mvc/controller_handle_test.go b/mvc/controller_handle_test.go index 7ad653d2c7..59038b4239 100644 --- a/mvc/controller_handle_test.go +++ b/mvc/controller_handle_test.go @@ -28,7 +28,7 @@ func (s *testServiceImpl) Say(message string) string { } type testControllerHandle struct { - Ctx context.Context + Ctx *context.Context Service testService reqField string @@ -110,7 +110,7 @@ func (c *testControllerHandle) CustomWithParameters(param1, param2 string) strin type testSmallController struct{} // test ctx + id in the same time. -func (c *testSmallController) GetHiParamEmptyInputWithCtxBy(ctx context.Context, id string) string { +func (c *testSmallController) GetHiParamEmptyInputWithCtxBy(ctx *context.Context, id string) string { return "empty in but served with ctx.Params.Get('param2')= " + ctx.Params().Get("param2") + " == id == " + id } diff --git a/mvc/controller_method_result_test.go b/mvc/controller_method_result_test.go index d3583afe62..163bf1b11e 100644 --- a/mvc/controller_method_result_test.go +++ b/mvc/controller_method_result_test.go @@ -12,7 +12,7 @@ import ( ) type testControllerMethodResult struct { - Ctx context.Context + Ctx *context.Context } func (c *testControllerMethodResult) Get() Result { @@ -103,7 +103,7 @@ func TestControllerMethodResult(t *testing.T) { } type testControllerMethodResultTypes struct { - Ctx context.Context + Ctx *context.Context } func (c *testControllerMethodResultTypes) GetText() string { @@ -132,7 +132,7 @@ type testControllerMethodCustomResult struct { } // The only one required function to make that a custom Response dispatcher. -func (r testControllerMethodCustomResult) Dispatch(ctx context.Context) { +func (r testControllerMethodCustomResult) Dispatch(ctx *context.Context) { ctx.HTML(r.HTML) } @@ -227,11 +227,11 @@ type testControllerViewResultRespectCtxViewData struct { T *testing.T } -func (t *testControllerViewResultRespectCtxViewData) BeginRequest(ctx context.Context) { +func (t *testControllerViewResultRespectCtxViewData) BeginRequest(ctx *context.Context) { ctx.ViewData("name_begin", "iris_begin") } -func (t *testControllerViewResultRespectCtxViewData) EndRequest(ctx context.Context) { +func (t *testControllerViewResultRespectCtxViewData) EndRequest(ctx *context.Context) { // check if data is not overridden by return View {Data: context.Map...} dataWritten := ctx.GetViewData() diff --git a/mvc/controller_test.go b/mvc/controller_test.go index 3ba6d3025c..62233b35c5 100644 --- a/mvc/controller_test.go +++ b/mvc/controller_test.go @@ -14,10 +14,10 @@ import ( ) type testController struct { - Ctx context.Context + Ctx *context.Context } -var writeMethod = func(ctx context.Context) { +var writeMethod = func(ctx *context.Context) { ctx.Writef(ctx.Method()) } @@ -58,8 +58,8 @@ func (c *testController) Trace() { } type ( - testControllerAll struct{ Ctx context.Context } - testControllerAny struct{ Ctx context.Context } // exactly the same as All. + testControllerAll struct{ Ctx *context.Context } + testControllerAny struct{ Ctx *context.Context } // exactly the same as All. ) func (c *testControllerAll) All() { @@ -92,7 +92,7 @@ func TestControllerMethodFuncs(t *testing.T) { } type testControllerBeginAndEndRequestFunc struct { - Ctx context.Context + Ctx *context.Context Username string } @@ -101,12 +101,12 @@ type testControllerBeginAndEndRequestFunc struct { // // useful when more than one methods using the // same request values or context's function calls. -func (c *testControllerBeginAndEndRequestFunc) BeginRequest(ctx context.Context) { +func (c *testControllerBeginAndEndRequestFunc) BeginRequest(ctx *context.Context) { c.Username = ctx.Params().Get("username") } // called after every method (Get() or Post()). -func (c *testControllerBeginAndEndRequestFunc) EndRequest(ctx context.Context) { +func (c *testControllerBeginAndEndRequestFunc) EndRequest(ctx *context.Context) { ctx.Writef("done") // append "done" to the response } @@ -152,7 +152,7 @@ func TestControllerBeginAndEndRequestFuncBindMiddleware(t *testing.T) { "bill": true, "whoisyourdaddy": false, } - middlewareCheck := func(ctx context.Context) { + middlewareCheck := func(ctx *context.Context) { for username, allow := range usernames { if ctx.Params().Get("username") == username && allow { ctx.Next() @@ -197,7 +197,7 @@ type Model struct { } type testControllerEndRequestAwareness struct { - Ctx context.Context + Ctx *context.Context } func (c *testControllerEndRequestAwareness) Get() { @@ -209,7 +209,7 @@ func (c *testControllerEndRequestAwareness) Get() { }) } -func writeModels(ctx context.Context, names ...string) { +func writeModels(ctx *context.Context, names ...string) { if expected, got := len(names), len(ctx.GetViewData()); expected != got { ctx.Writef("expected view data length: %d but got: %d for names: %s", expected, got, names) return @@ -233,8 +233,8 @@ func writeModels(ctx context.Context, names ...string) { } } -func (c *testControllerEndRequestAwareness) BeginRequest(ctx context.Context) {} -func (c *testControllerEndRequestAwareness) EndRequest(ctx context.Context) { +func (c *testControllerEndRequestAwareness) BeginRequest(ctx *context.Context) {} +func (c *testControllerEndRequestAwareness) EndRequest(ctx *context.Context) { writeModels(ctx, "TestModel", "myModel") } @@ -259,7 +259,7 @@ type testBindType struct { } type testControllerBindStruct struct { - Ctx context.Context + Ctx *context.Context // should start with upper letter of course TitlePointer *testBindType // should have the value of the "myTitlePtr" on test @@ -335,7 +335,7 @@ func (c *testCtrl0) Get() string { return c.Ctx.Params().Get("username") } -func (c *testCtrl0) EndRequest(ctx context.Context) { +func (c *testCtrl0) EndRequest(ctx *context.Context) { if c.TitlePointer == nil { ctx.Writef("\nTitlePointer is nil!\n") } else { @@ -347,7 +347,7 @@ func (c *testCtrl0) EndRequest(ctx context.Context) { } type testCtrl00 struct { - Ctx context.Context + Ctx *context.Context testCtrl000 } @@ -361,8 +361,8 @@ type testCtrl000 struct { type testCtrl0000 struct { } -func (c *testCtrl0000) BeginRequest(ctx context.Context) {} -func (c *testCtrl0000) EndRequest(ctx context.Context) { +func (c *testCtrl0000) BeginRequest(ctx *context.Context) {} +func (c *testCtrl0000) EndRequest(ctx *context.Context) { ctx.Writef("finish") } @@ -385,8 +385,8 @@ func TestControllerInsideControllerRecursively(t *testing.T) { type testControllerRelPathFromFunc struct{} -func (c *testControllerRelPathFromFunc) BeginRequest(ctx context.Context) {} -func (c *testControllerRelPathFromFunc) EndRequest(ctx context.Context) { +func (c *testControllerRelPathFromFunc) BeginRequest(ctx *context.Context) {} +func (c *testControllerRelPathFromFunc) EndRequest(ctx *context.Context) { ctx.Writef("%s:%s", ctx.Method(), ctx.Path()) } @@ -565,7 +565,7 @@ func (c *testControllerRequestScopedDependencies) GetCustomContext() string { return c.MyContext.OtherField } -func newRequestDep1(ctx context.Context) *testCustomStruct { +func newRequestDep1(ctx *context.Context) *testCustomStruct { return &testCustomStruct{ Name: ctx.URLParam("name"), Age: ctx.URLParamIntDefault("age", 0), @@ -573,11 +573,11 @@ func newRequestDep1(ctx context.Context) *testCustomStruct { } type testMyContext struct { - Context context.Context + Context *context.Context OtherField string } -func newRequestDep2(ctx context.Context) *testMyContext { +func newRequestDep2(ctx *context.Context) *testMyContext { return &testMyContext{ Context: ctx, OtherField: "test", diff --git a/mvc/grpc.go b/mvc/grpc.go index 01ae04e16a..e488a74cb6 100644 --- a/mvc/grpc.go +++ b/mvc/grpc.go @@ -40,7 +40,7 @@ var _ Option = GRPC{} func (g GRPC) Apply(c *ControllerActivator) { defer c.Activated() - pre := func(ctx context.Context) { + pre := func(ctx *context.Context) { if ctx.IsGRPC() { // gRPC, consumes and produces protobuf. g.Server.ServeHTTP(ctx.ResponseWriter(), ctx.Request()) ctx.StopExecution() diff --git a/mvc/mvc.go b/mvc/mvc.go index 72df3dc89b..7309934de0 100644 --- a/mvc/mvc.go +++ b/mvc/mvc.go @@ -288,9 +288,9 @@ func (app *Application) handle(controller interface{}, options ...Option) *Contr // HandleError registers a `hero.ErrorHandlerFunc` which will be fired when // application's controllers' functions returns an non-nil error. // Each controller can override it by implementing the `hero.ErrorHandler`. -func (app *Application) HandleError(handler func(ctx context.Context, err error)) *Application { +func (app *Application) HandleError(handler func(ctx *context.Context, err error)) *Application { errorHandler := hero.ErrorHandlerFunc(handler) - app.container.GetErrorHandler = func(context.Context) hero.ErrorHandler { + app.container.GetErrorHandler = func(*context.Context) hero.ErrorHandler { return errorHandler } return app diff --git a/mvc/versioning.go b/mvc/versioning.go index c8142325d6..0e9085fd24 100644 --- a/mvc/versioning.go +++ b/mvc/versioning.go @@ -26,7 +26,7 @@ func Version(version string) OptionFunc { return func(c *ControllerActivator) { c.Router().SetRegisterRule(router.RouteOverlap) // required for this feature. - c.Use(func(ctx context.Context) { + c.Use(func(ctx *context.Context) { if !versioning.Match(ctx, version) { ctx.StopExecution() return @@ -42,7 +42,7 @@ func Version(version string) OptionFunc { // a newer version of that specific resource is available instead. func Deprecated(options DeprecationOptions) OptionFunc { return func(c *ControllerActivator) { - c.Use(func(ctx context.Context) { + c.Use(func(ctx *context.Context) { versioning.WriteDeprecated(ctx, options) ctx.Next() }) diff --git a/sessions/config.go b/sessions/config.go index 269f214fb3..e673d50ef1 100644 --- a/sessions/config.go +++ b/sessions/config.go @@ -54,7 +54,7 @@ type ( // return a unique session id. // By default we will use a uuid impl package to generate // that, but developers can change that with simple assignment. - SessionIDGenerator func(ctx context.Context) string + SessionIDGenerator func(ctx *context.Context) string // DisableSubdomainPersistence set it to true in order dissallow your subdomains to have access to the session cookie // @@ -70,7 +70,7 @@ func (c Config) Validate() Config { } if c.SessionIDGenerator == nil { - c.SessionIDGenerator = func(context.Context) string { + c.SessionIDGenerator = func(*context.Context) string { id, _ := uuid.NewRandom() return id.String() } diff --git a/sessions/sessions.go b/sessions/sessions.go index 9bed639c31..1eb061ba4e 100644 --- a/sessions/sessions.go +++ b/sessions/sessions.go @@ -44,7 +44,7 @@ func (s *Sessions) GetCookieOptions() []context.CookieOption { } // updateCookie gains the ability of updating the session browser cookie to any method which wants to update it -func (s *Sessions) updateCookie(ctx context.Context, sid string, expires time.Duration, options ...context.CookieOption) { +func (s *Sessions) updateCookie(ctx *context.Context, sid string, expires time.Duration, options ...context.CookieOption) { cookie := &http.Cookie{} // The RFC makes no mention of encoding url value, so here I think to encode both sessionid key and the value using the safe(to put and to use as cookie) url-encoding @@ -76,7 +76,7 @@ func (s *Sessions) updateCookie(ctx context.Context, sid string, expires time.Du // on MVC and APIContainer as well. // // NOTE: Use `app.Use(sess.Handler())` instead, avoid using `Start` manually. -func (s *Sessions) Start(ctx context.Context, cookieOptions ...context.CookieOption) *Session { +func (s *Sessions) Start(ctx *context.Context, cookieOptions ...context.CookieOption) *Session { cookieValue := ctx.GetCookie(s.config.Cookie, cookieOptions...) if cookieValue == "" { // cookie doesn't exist, let's generate a session and set a cookie. @@ -123,7 +123,7 @@ func (s *Sessions) Handler(cookieOptions ...context.CookieOption) context.Handle requestOptions = append(requestOptions, context.CookieEncoding(s.config.Encoding, s.config.Cookie)) } - return func(ctx context.Context) { + return func(ctx *context.Context) { ctx.AddCookieOptions(requestOptions...) // request life-cycle options. session := s.Start(ctx, cookieOptions...) // this cookie's end-developer's custom options. @@ -142,7 +142,7 @@ func (s *Sessions) Handler(cookieOptions ...context.CookieOption) context.Handle // Note: It will return nil if the session got destroyed by the same request. // If you need to destroy and start a new session in the same request you need to call // sessions manager's `Start` method after Destroy. -func Get(ctx context.Context) *Session { +func Get(ctx *context.Context) *Session { if v := ctx.Values().Get(sessionContextKey); v != nil { if sess, ok := v.(*Session); ok { return sess @@ -154,14 +154,14 @@ func Get(ctx context.Context) *Session { } // StartWithPath same as `Start` but it explicitly accepts the cookie path option. -func (s *Sessions) StartWithPath(ctx context.Context, path string) *Session { +func (s *Sessions) StartWithPath(ctx *context.Context, path string) *Session { return s.Start(ctx, context.CookiePath(path)) } // ShiftExpiration move the expire date of a session to a new date // by using session default timeout configuration. // It will return `ErrNotImplemented` if a database is used and it does not support this feature, yet. -func (s *Sessions) ShiftExpiration(ctx context.Context, cookieOptions ...context.CookieOption) error { +func (s *Sessions) ShiftExpiration(ctx *context.Context, cookieOptions ...context.CookieOption) error { return s.UpdateExpiration(ctx, s.config.Expires, cookieOptions...) } @@ -169,7 +169,7 @@ func (s *Sessions) ShiftExpiration(ctx context.Context, cookieOptions ...context // by using timeout value passed by `expires` receiver. // It will return `ErrNotFound` when trying to update expiration on a non-existence or not valid session entry. // It will return `ErrNotImplemented` if a database is used and it does not support this feature, yet. -func (s *Sessions) UpdateExpiration(ctx context.Context, expires time.Duration, cookieOptions ...context.CookieOption) error { +func (s *Sessions) UpdateExpiration(ctx *context.Context, expires time.Duration, cookieOptions ...context.CookieOption) error { cookieValue := ctx.GetCookie(s.config.Cookie) if cookieValue == "" { return ErrNotFound @@ -203,7 +203,7 @@ func (s *Sessions) OnDestroy(listeners ...DestroyListener) { // Next calls of `sessions.Get` will occur to a nil Session, // use `Sessions#Start` method for renewal // or use the Session's Destroy method which does keep the session entry with its values cleared. -func (s *Sessions) Destroy(ctx context.Context) { +func (s *Sessions) Destroy(ctx *context.Context) { cookieValue := ctx.GetCookie(s.config.Cookie) if cookieValue == "" { // nothing to destroy return diff --git a/sessions/sessions_test.go b/sessions/sessions_test.go index 7dfecaecc9..f9c4b2cf77 100644 --- a/sessions/sessions_test.go +++ b/sessions/sessions_test.go @@ -31,7 +31,7 @@ func testSessions(t *testing.T, app *iris.Application) { "Secret": "dsads£2132215£%%Ssdsa", } - writeValues := func(ctx context.Context) { + writeValues := func(ctx *context.Context) { s := sessions.Get(ctx) sessValues := s.GetAll() @@ -45,7 +45,7 @@ func testSessions(t *testing.T, app *iris.Application) { app.Party("subdomain.").Get("/get", writeValues) } - app.Post("/set", func(ctx context.Context) { + app.Post("/set", func(ctx *context.Context) { s := sessions.Get(ctx) vals := make(map[string]interface{}) if err := ctx.ReadJSON(&vals); err != nil { @@ -56,16 +56,16 @@ func testSessions(t *testing.T, app *iris.Application) { } }) - app.Get("/get", func(ctx context.Context) { + app.Get("/get", func(ctx *context.Context) { writeValues(ctx) }) - app.Get("/clear", func(ctx context.Context) { + app.Get("/clear", func(ctx *context.Context) { sessions.Get(ctx).Clear() writeValues(ctx) }) - app.Get("/destroy", func(ctx context.Context) { + app.Get("/destroy", func(ctx *context.Context) { session := sessions.Get(ctx) if session.IsNew() { t.Fatal("expected session not to be nil on destroy") @@ -82,16 +82,16 @@ func testSessions(t *testing.T, app *iris.Application) { }) // cookie should be new. - app.Get("/after_destroy_renew", func(ctx context.Context) { + app.Get("/after_destroy_renew", func(ctx *context.Context) { isNew := sessions.Get(ctx).IsNew() ctx.Writef("%v", isNew) }) - app.Get("/multi_start_set_get", func(ctx context.Context) { + app.Get("/multi_start_set_get", func(ctx *context.Context) { s := sessions.Get(ctx) s.Set("key", "value") ctx.Next() - }, func(ctx context.Context) { + }, func(ctx *context.Context) { s := sessions.Get(ctx) _, err := ctx.Writef(s.GetString("key")) if err != nil { @@ -140,12 +140,12 @@ func TestFlashMessages(t *testing.T) { "Secret": "dsads£2132215£%%Ssdsa", } - writeValues := func(ctx context.Context, values map[string]interface{}) error { + writeValues := func(ctx *context.Context, values map[string]interface{}) error { _, err := ctx.JSON(values) return err } - app.Post("/set", func(ctx context.Context) { + app.Post("/set", func(ctx *context.Context) { vals := make(map[string]interface{}) if err := ctx.ReadJSON(&vals); err != nil { t.Fatalf("Cannot readjson. Trace %s", err.Error()) @@ -158,7 +158,7 @@ func TestFlashMessages(t *testing.T) { ctx.StatusCode(iris.StatusOK) }) - writeFlashValues := func(ctx context.Context) { + writeFlashValues := func(ctx *context.Context) { s := sess.Start(ctx) flashes := s.GetFlashes() @@ -167,23 +167,23 @@ func TestFlashMessages(t *testing.T) { } } - app.Get("/get_single", func(ctx context.Context) { + app.Get("/get_single", func(ctx *context.Context) { s := sess.Start(ctx) flashMsgString := s.GetFlashString(valueSingleKey) ctx.WriteString(flashMsgString) }) - app.Get("/get", func(ctx context.Context) { + app.Get("/get", func(ctx *context.Context) { writeFlashValues(ctx) }) - app.Get("/clear", func(ctx context.Context) { + app.Get("/clear", func(ctx *context.Context) { s := sess.Start(ctx) s.ClearFlashes() writeFlashValues(ctx) }) - app.Get("/destroy", func(ctx context.Context) { + app.Get("/destroy", func(ctx *context.Context) { sess.Destroy(ctx) writeFlashValues(ctx) ctx.StatusCode(iris.StatusOK) @@ -191,7 +191,7 @@ func TestFlashMessages(t *testing.T) { }) // request cookie should be empty - app.Get("/after_destroy", func(ctx context.Context) { + app.Get("/after_destroy", func(ctx *context.Context) { ctx.StatusCode(iris.StatusOK) }) @@ -233,7 +233,7 @@ func TestSessionsUpdateExpiration(t *testing.T) { Logged bool `json:"logged"` } - var writeResponse = func(ctx context.Context) { + var writeResponse = func(ctx *context.Context) { session := sessions.Get(ctx) ctx.JSON(response{ SessionID: session.ID(), @@ -241,7 +241,7 @@ func TestSessionsUpdateExpiration(t *testing.T) { }) } - app.Get("/get", func(ctx context.Context) { + app.Get("/get", func(ctx *context.Context) { writeResponse(ctx) }) diff --git a/versioning/deprecation.go b/versioning/deprecation.go index 6d13b87b95..7e28d42111 100644 --- a/versioning/deprecation.go +++ b/versioning/deprecation.go @@ -32,7 +32,7 @@ var DefaultDeprecationOptions = DeprecationOptions{ // It can be used inside a middleware. // // See `Deprecated` to wrap an existing handler instead. -func WriteDeprecated(ctx context.Context, options DeprecationOptions) { +func WriteDeprecated(ctx *context.Context, options DeprecationOptions) { if options.WarnMessage == "" { options.WarnMessage = DefaultDeprecationOptions.WarnMessage } @@ -52,7 +52,7 @@ func WriteDeprecated(ctx context.Context, options DeprecationOptions) { // Deprecated can be used to tell the clients that // a newer version of that specific resource is available instead. func Deprecated(handler context.Handler, options DeprecationOptions) context.Handler { - return func(ctx context.Context) { + return func(ctx *context.Context) { WriteDeprecated(ctx, options) handler(ctx) } diff --git a/versioning/version.go b/versioning/version.go index ae3d30ee8a..d5ea0b7434 100644 --- a/versioning/version.go +++ b/versioning/version.go @@ -33,7 +33,7 @@ const ( // NotFoundHandler is the default version not found handler that // is executed from `NewMatcher` when no version is registered as available to dispatch a resource. -var NotFoundHandler = func(ctx context.Context) { +var NotFoundHandler = func(ctx *context.Context) { // 303 is an option too, // end-dev has the chance to change that behavior by using the NotFound in the map: // @@ -60,7 +60,7 @@ var NotFoundHandler = func(ctx context.Context) { // for versions (see `Key` for further details on that). // // See `SetVersion` too. -func GetVersion(ctx context.Context) string { +func GetVersion(ctx *context.Context) string { // firstly by context store, if manually set by a middleware. version := ctx.Values().GetString(ctx.Application().ConfigurationReadOnly().GetVersionContextKey()) if version != "" { @@ -104,6 +104,6 @@ func GetVersion(ctx context.Context) string { // SetVersion force-sets the API Version. // It can be used inside a middleware. // See `GetVersion` too. -func SetVersion(ctx context.Context, constraint string) { +func SetVersion(ctx *context.Context, constraint string) { ctx.Values().Set(ctx.Application().ConfigurationReadOnly().GetVersionContextKey(), constraint) } diff --git a/versioning/versioning.go b/versioning/versioning.go index 886b766628..682636e81e 100644 --- a/versioning/versioning.go +++ b/versioning/versioning.go @@ -33,7 +33,7 @@ func check(v string, is string) (string, bool) { // // If matched then it sets the "X-API-Version" response header and // stores the matched version into Context (see `GetVersion` too). -func Match(ctx context.Context, expectedVersion string) bool { +func Match(ctx *context.Context, expectedVersion string) bool { versionString, matched := check(GetVersion(ctx), expectedVersion) if !matched { return false @@ -57,7 +57,7 @@ type Map map[string]context.Handler func NewMatcher(versions Map) context.Handler { constraintsHandlers, notFoundHandler := buildConstraints(versions) - return func(ctx context.Context) { + return func(ctx *context.Context) { versionString := GetVersion(ctx) if versionString == "" || versionString == NotFound { notFoundHandler(ctx) diff --git a/view/jet.go b/view/jet.go index cc24f9ee1b..6b92ded752 100644 --- a/view/jet.go +++ b/view/jet.go @@ -331,7 +331,7 @@ const JetRuntimeVarsContextKey = "iris.jetvarmap" // // Usage: view.AddJetRuntimeVars(ctx, view.JetRuntimeVars{...}). // See `JetEngine.AddRuntimeVars` too. -func AddJetRuntimeVars(ctx context.Context, jetVarMap JetRuntimeVars) { +func AddJetRuntimeVars(ctx *context.Context, jetVarMap JetRuntimeVars) { if v := ctx.Values().Get(JetRuntimeVarsContextKey); v != nil { if vars, ok := v.(JetRuntimeVars); ok { for key, value := range jetVarMap { @@ -350,7 +350,7 @@ func AddJetRuntimeVars(ctx context.Context, jetVarMap JetRuntimeVars) { // // Usage: view.AddJetRuntimeVars(ctx, view.JetRuntimeVars{...}). // See `view.AddJetRuntimeVars` if package-level access is more meanful to the code flow. -func (s *JetEngine) AddRuntimeVars(ctx context.Context, vars JetRuntimeVars) { +func (s *JetEngine) AddRuntimeVars(ctx *context.Context, vars JetRuntimeVars) { AddJetRuntimeVars(ctx, vars) } @@ -363,7 +363,7 @@ func (s *JetEngine) ExecuteWriter(w io.Writer, filename string, layout string, b var vars JetRuntimeVars - if ctx, ok := w.(context.Context); ok { + if ctx, ok := w.(*context.Context); ok { runtimeVars := ctx.Values().Get(JetRuntimeVarsContextKey) if runtimeVars != nil { if jetVars, ok := runtimeVars.(JetRuntimeVars); ok { diff --git a/websocket/websocket.go b/websocket/websocket.go index f26e239a71..5428baffd0 100644 --- a/websocket/websocket.go +++ b/websocket/websocket.go @@ -37,7 +37,7 @@ var ( New = neffos.New // DefaultIDGenerator returns a universal unique identifier for a new connection. // It's the default `IDGenerator` if missing. - DefaultIDGenerator = func(ctx context.Context) string { + DefaultIDGenerator = func(ctx *context.Context) string { return neffos.DefaultIDGenerator(ctx.ResponseWriter(), ctx.Request()) } @@ -138,10 +138,10 @@ func SetDefaultUnmarshaler(fn func(data []byte, v interface{}) error) { } // IDGenerator is an iris-specific IDGenerator for new connections. -type IDGenerator func(context.Context) string +type IDGenerator func(*context.Context) string -func wrapIDGenerator(idGen IDGenerator) func(ctx context.Context) neffos.IDGenerator { - return func(ctx context.Context) neffos.IDGenerator { +func wrapIDGenerator(idGen IDGenerator) func(ctx *context.Context) neffos.IDGenerator { + return func(ctx *context.Context) neffos.IDGenerator { return func(w http.ResponseWriter, r *http.Request) string { return idGen(ctx) } @@ -157,7 +157,7 @@ func Handler(s *neffos.Server, IDGenerator ...IDGenerator) context.Handler { idGen = IDGenerator[0] } - return func(ctx context.Context) { + return func(ctx *context.Context) { if ctx.IsStopped() { return } @@ -167,7 +167,7 @@ func Handler(s *neffos.Server, IDGenerator ...IDGenerator) context.Handler { // Upgrade upgrades the request and returns a new websocket Conn. // Use `Handler` for higher-level implementation instead. -func Upgrade(ctx context.Context, idGen IDGenerator, s *neffos.Server) *neffos.Conn { +func Upgrade(ctx *context.Context, idGen IDGenerator, s *neffos.Server) *neffos.Conn { conn, _ := s.Upgrade(ctx.ResponseWriter(), ctx.Request(), func(socket neffos.Socket) neffos.Socket { return &socketWrapper{ Socket: socket, @@ -180,11 +180,11 @@ func Upgrade(ctx context.Context, idGen IDGenerator, s *neffos.Server) *neffos.C type socketWrapper struct { neffos.Socket - ctx context.Context + ctx *context.Context } // GetContext returns the Iris Context from a websocket connection. -func GetContext(c *neffos.Conn) context.Context { +func GetContext(c *neffos.Conn) *context.Context { if sw, ok := c.Socket().(*socketWrapper); ok { return sw.ctx }