(#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: ad1c61bf968059510c6be9e7f2cceec7da70ba17
This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-07-10 23:21:09 +03:00
parent 645da2b2ef
commit 0f113dfcda
112 changed files with 2119 additions and 3390 deletions

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:

3
NOTICE
View File

@ -23,6 +23,9 @@ Revision ID: d1c07411df0bb21f6b21f5b5d9325fac6f29c911
bluemonday 0a75d7616912ab9 https://github.com/microcosm-cc/
beb9cc6f7283ec1 bluemonday
917c61b135
brotli c3da72aa01ed78f https://github.com/andybalholm/brotli
164593b9624fd91
d25082d2d2
closestmatch 1fbe626be92eb4c https://github.com/schollz/closestmatch
347d182cae9f8f0
0a046bf2f4

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

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

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

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

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

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.

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.

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

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.

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

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
}

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

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

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.

View File

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

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

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

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:

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.

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)

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/

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

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

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

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

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

8
cache/browser.go vendored
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()

16
cache/cache_test.go vendored
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))

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

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

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
}

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

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

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
}

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
}

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
}

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

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
cli.go Normal file
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
}

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

206
context/accept_header.go Normal file
View File

@ -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 = <any 8-bit sequence of data>
// CHAR = <any US-ASCII character (octets 0 - 127)>
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
// CR = <US-ASCII CR, carriage return (13)>
// LF = <US-ASCII LF, linefeed (10)>
// SP = <US-ASCII SP, space (32)>
// HT = <US-ASCII HT, horizontal-tab (9)>
// <"> = <US-ASCII double-quote mark (34)>
// CRLF = CR LF
// LWS = [CRLF] 1*( SP | HT )
// TEXT = <any OCTET except CTLs, but including LWS>
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
// token = 1*<any CHAR except CTLs or separators>
// qdtext = <any TEXT except <">>
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
}
}

View File

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

273
context/compress.go Normal file
View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) <T> {
// return func(ctx *Context) <T> {
// return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(<T>)
// }
// }
@ -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))}
})
//
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <a> 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("<a href=\"%s\">%s</a>\n", url.String(), html.EscapeString(name))
_, err = ctx.Writef("<a href=\"%s\"%s>%s</a>\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 }}
<tr>
<td>{{ $idx }}</td>
<td><a href="{{ $file.Path }}" title="{{ $file.ModTime }}">{{ $file.Name }}</a></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 }}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

4
go.mod
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

559
iris.go
View File

@ -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.
// Byte unit helpers.
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
B = 1 << (10 * iota)
KB
MB
GB
TB
PB
EB
)
// 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`.
const (
MethodGet = "GET"
MethodPost = "POST"
MethodPut = "PUT"
MethodDelete = "DELETE"
MethodConnect = "CONNECT"
MethodHead = "HEAD"
MethodPatch = "PATCH"
MethodOptions = "OPTIONS"
MethodTrace = "TRACE"
)
// 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(`<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>")
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>.
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))
}
publicAddr := publicAddrs[0]
// to make subdomains resolution still based on this new remote, public addresses.
app.config.vhost = publicAddr[strings.Index(publicAddr, "://")+3:]
if t.Addr == "" {
t.Addr = su.Server.Addr
}
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
})
})
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 != "" {

View File

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

View File

@ -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 = `<form action="%s" method="POST">
// 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 = `<form action="%s" method="POST">
// }
//
// Method: "GET" | Path: "/contact"
// func getContact(ctx context.Context) {
// func getContact(ctx *context.Context) {
// // render the recaptcha form
// ctx.HTML(recaptcha.GetFormHTML(recaptchaPublic, "/contact"))
// }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More