Browse Source

(#1554) Add support for all common compressions (write and read)

- Remove the context.Context interface and export the *context, the iris.Context now points to the pointer\nSupport compression and rate limiting in the FileServer\nBit of code organisation


Former-commit-id: ad1c61bf96
tags/v0.0.1
Gerasimos (Makis) Maropoulos 1 year ago
parent
commit
0f113dfcda
100 changed files with 2040 additions and 3311 deletions
  1. +13
    -5
      HISTORY.md
  2. +5
    -2
      NOTICE
  3. +4
    -5
      _examples/README.md
  4. +112
    -0
      _examples/compression/client-using-iris/main.go
  5. +102
    -0
      _examples/compression/client/main.go
  6. +64
    -0
      _examples/compression/main.go
  7. +41
    -0
      _examples/compression/main_test.go
  8. +0
    -3
      _examples/convert-handlers/negroni-like/main.go
  9. +0
    -3
      _examples/convert-handlers/nethttp/main.go
  10. +1
    -1
      _examples/file-server/basic/main.go
  11. +1
    -1
      _examples/file-server/file-server/main.go
  12. +4
    -0
      _examples/file-server/file-server/views/dirlist.html
  13. +0
    -44
      _examples/request-body/read-gzip/main.go
  14. +0
    -38
      _examples/request-body/read-gzip/main_test.go
  15. +0
    -24
      _examples/response-writer/write-gzip/main.go
  16. +0
    -90
      _examples/routing/custom-context/method-overriding/main.go
  17. +0
    -1
      _examples/routing/custom-context/method-overriding/view/hi.html
  18. +0
    -103
      _examples/routing/custom-context/new-implementation/main.go
  19. +0
    -26
      _examples/routing/custom-context/new-implementation/main_test.go
  20. +1
    -1
      _examples/routing/custom-router/main.go
  21. +1
    -1
      _examples/routing/dynamic-path/main.go
  22. +4
    -6
      _examples/view/herotemplate/app.go
  23. +3
    -3
      _examples/view/overview/main.go
  24. +3
    -3
      _examples/view/quicktemplate/controllers/execute_template.go
  25. +1
    -1
      _examples/view/template_html_1/main.go
  26. +11
    -11
      _examples/view/template_jet_1_embedded/bindata.go
  27. +1
    -1
      _examples/webassembly/main.go
  28. +397
    -3
      aliases.go
  29. +4
    -4
      cache/browser.go
  30. +8
    -8
      cache/cache_test.go
  31. +1
    -1
      cache/client/client.go
  32. +3
    -3
      cache/client/handler.go
  33. +2
    -2
      cache/client/rule/chained.go
  34. +2
    -2
      cache/client/rule/conditional.go
  35. +2
    -2
      cache/client/rule/header.go
  36. +2
    -2
      cache/client/rule/not_satisfied.go
  37. +3
    -5
      cache/client/rule/rule.go
  38. +2
    -2
      cache/client/rule/satisfied.go
  39. +5
    -7
      cache/client/rule/validator.go
  40. +1
    -1
      cache/client/ruleset.go
  41. +120
    -0
      cli.go
  42. +11
    -223
      configuration.go
  43. +206
    -0
      context/accept_header.go
  44. +3
    -3
      context/application.go
  45. +273
    -0
      context/compress.go
  46. +317
    -1557
      context/context.go
  47. +0
    -231
      context/gzip_response_writer.go
  48. +3
    -3
      context/handler.go
  49. +1
    -1
      context/i18n.go
  50. +7
    -22
      context/pool.go
  51. +3
    -3
      context/problem.go
  52. +16
    -16
      context/request_params.go
  53. +1
    -16
      context/response_recorder.go
  54. +2
    -31
      context/response_writer.go
  55. +9
    -9
      context/transaction.go
  56. +3
    -3
      core/handlerconv/from_std.go
  57. +1
    -1
      core/handlerconv/from_std_test.go
  58. +3
    -3
      core/host/proxy_test.go
  59. +5
    -5
      core/router/api_builder.go
  60. +1
    -1
      core/router/api_builder_benchmark_test.go
  61. +2
    -2
      core/router/api_container.go
  62. +56
    -61
      core/router/fs.go
  63. +16
    -20
      core/router/handler.go
  64. +1
    -1
      core/router/handler_execution_rules.go
  65. +1
    -1
      core/router/handler_execution_rules_test.go
  66. +4
    -1
      core/router/party.go
  67. +2
    -2
      core/router/router.go
  68. +7
    -7
      core/router/router_handlers_order_test.go
  69. +2
    -2
      core/router/router_test.go
  70. +3
    -3
      core/router/router_wildcard_root_test.go
  71. +6
    -6
      core/router/status_test.go
  72. +3
    -1
      go.mod
  73. +3
    -3
      hero/binding.go
  74. +12
    -12
      hero/binding_test.go
  75. +9
    -9
      hero/container.go
  76. +7
    -7
      hero/dependency.go
  77. +8
    -8
      hero/dependency_test.go
  78. +11
    -13
      hero/func_result.go
  79. +9
    -9
      hero/handler.go
  80. +1
    -1
      hero/param_test.go
  81. +2
    -2
      hero/reflect.go
  82. +2
    -2
      hero/struct.go
  83. +3
    -3
      i18n/i18n.go
  84. +24
    -535
      iris.go
  85. +2
    -2
      macro/handler/handler.go
  86. +2
    -2
      middleware/basicauth/basicauth.go
  87. +1
    -1
      middleware/basicauth/config.go
  88. +5
    -5
      middleware/hcaptcha/hcaptcha.go
  89. +12
    -12
      middleware/jwt/jwt.go
  90. +3
    -3
      middleware/logger/config.go
  91. +1
    -1
      middleware/logger/logger.go
  92. +1
    -1
      middleware/pprof/pprof.go
  93. +8
    -8
      middleware/rate/rate.go
  94. +4
    -4
      middleware/recaptcha/recaptcha.go
  95. +2
    -2
      middleware/recover/recover.go
  96. +4
    -4
      middleware/requestid/requestid.go
  97. +2
    -2
      middleware/requestid/requestid_test.go
  98. +3
    -3
      mvc/controller.go
  99. +2
    -2
      mvc/controller_handle_test.go
  100. +5
    -5
      mvc/controller_method_result_test.go

+ 13
- 5
HISTORY.md View File

@@ -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 <https://go-review.googlesource.com/c/go/+/186927/>.
- 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:


+ 5
- 2
NOTICE View File

@@ -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

+ 4
- 5
_examples/README.md View File

@@ -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


+ 112
- 0
_examples/compression/client-using-iris/main.go View File

@@ -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))
}

+ 102
- 0
_examples/compression/client/main.go View File

@@ -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))
}

+ 64
- 0
_examples/compression/main.go View File

@@ -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()
}
*/

+ 41
- 0
_examples/compression/main_test.go View File

@@ -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)
}
}

+ 0
- 3
_examples/convert-handlers/negroni-like/main.go View File

@@ -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.

+ 0
- 3
_examples/convert-handlers/nethttp/main.go View File

@@ -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.

+ 1
- 1
_examples/file-server/basic/main.go View File

@@ -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).


+ 1
- 1
_examples/file-server/file-server/main.go View File

@@ -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.


+ 4
- 0
_examples/file-server/file-server/views/dirlist.html View File

@@ -83,7 +83,11 @@
{{ range $idx, $file := .Files }}
<tr>
<td>{{ $idx }}</td>
{{ if $file.Download }}
<td><a href="{{ $file.Path }}" title="{{ $file.ModTime }}" download>{{ $file.Name }}</a></td>
{{ else }}
<td><a href="{{ $file.Path }}" title="{{ $file.ModTime }}">{{ $file.Name }}</a></td>
{{ end }}
{{ if $file.Info.IsDir }}
<td>Dir</td>
{{ else }}


+ 0
- 44
_examples/request-body/read-gzip/main.go View File

@@ -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
}

+ 0
- 38
_examples/request-body/read-gzip/main_test.go View File

@@ -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)
}

+ 0
- 24
_examples/response-writer/write-gzip/main.go View File

@@ -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")
}

+ 0
- 90
_examples/routing/custom-context/method-overriding/main.go View File

@@ -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("<h1> Hello from my custom context's HTML! </h1>")
})

// 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.

+ 0
- 1
_examples/routing/custom-context/method-overriding/view/hi.html View File

@@ -1 +0,0 @@
<h1> Hi {{.firstname}} </h1>

+ 0
- 103
_examples/routing/custom-context/new-implementation/main.go View File

@@ -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("<b>" + text + "</b>")
}

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")
}

+ 0
- 26
_examples/routing/custom-context/new-implementation/main_test.go View File

@@ -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("<b>Hello from our *Context</b>")

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)
}

+ 1
- 1
_examples/routing/custom-router/main.go View File

@@ -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:


+ 1
- 1
_examples/routing/dynamic-path/main.go View File

@@ -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.


+ 4
- 6
_examples/view/herotemplate/app.go View File

@@ -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)



+ 3
- 3
_examples/view/overview/main.go View File

@@ -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/


+ 3
- 3
_examples/view/quicktemplate/controllers/execute_template.go View File

@@ -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)
}

+ 1
- 1
_examples/view/template_html_1/main.go View File

@@ -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


+ 11
- 11
_examples/view/template_jet_1_embedded/bindata.go View File

@@ -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, "/")...)...)
}


+ 1
- 1
_examples/webassembly/main.go View File

@@ -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")
})



+ 397
- 3
aliases.go View File

@@ -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

+ 4
- 4
cache/browser.go View File

@@ -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()


+ 8
- 8
cache/cache_test.go View File

@@ -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))


+ 1
- 1
cache/client/client.go View File

@@ -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) {


+ 3
- 3
cache/client/handler.go View File

@@ -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()


+ 2
- 2
cache/client/rule/chained.go View File

@@ -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
}


+ 2
- 2
cache/client/rule/conditional.go View File

@@ -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()
}

+ 2
- 2
cache/client/rule/header.go View File

@@ -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)
}

+ 2
- 2
cache/client/rule/not_satisfied.go View File

@@ -13,10 +13,10 @@ func NotSatisfied() Rule {
return &notSatisfiedRule{}
}

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
}

+ 3
- 5
cache/client/rule/rule.go View File

@@ -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
}

+ 2
- 2
cache/client/rule/satisfied.go View File

@@ -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
}

+ 5
- 7
cache/client/rule/validator.go View File

@@ -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) {


+ 1
- 1
cache/client/ruleset.go View File

@@ -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")
}

+ 120
- 0
cli.go View File

@@ -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(`<script>(function () {
const scheme = document.location.protocol == "https:" ? "wss" : "ws";
const endpoint = scheme + "://" + document.location.hostname + ":%d/livereload";

w = new WebSocket(endpoint);
w.onopen = function () {
console.info("LiveReload: initialization");
};
w.onclose = function () {
console.info("LiveReload: terminated");
};
w.onmessage = function (message) {
// NOTE: full-reload, at least for the moment. Also if backend changed its port then we will get 404 here.
window.location.reload();
};
}());</script>`, conf.LiveReload.Port))

bodyCloseTag := []byte("</body>")

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>.
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
}

+ 11
- 223
configuration.go View File

@@ -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 <YOUR_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