diff --git a/HISTORY.md b/HISTORY.md
index 95eed0fa6e..e604938d2b 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -19,15 +19,16 @@
Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready.
-**How to upgrade**: Open your command-line and execute this command: `go get github.com/kataras/iris@v11.2.0`.
+**How to upgrade**: Open your command-line and execute this command: `go get github.com/kataras/iris@master`.
+# Tu, 30 July 2019 | v11.2.3
-# We, 24 July 2019 | v11.2.1
+TODO:
-- https://github.com/kataras/iris/issues/1298
-- https://github.com/kataras/iris/issues/1207
+- Different parameter types in the same path (done).
+- [Content negotiation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation) (in-progress)
-## v11.2.2
+# We, 24 July 2019 | v11.2.2
Sessions as middleware:
@@ -47,6 +48,11 @@ app.Get("/path", func(ctx iris.Context){
- Add `Session.Len() int` to return the total number of stored values/entries.
- Make `Context.HTML` and `Context.Text` to accept an optional, variadic, `args ...interface{}` input arg(s) too.
+## v11.1.1
+
+- https://github.com/kataras/iris/issues/1298
+- https://github.com/kataras/iris/issues/1207
+
# Tu, 23 July 2019 | v11.2.0
-Read about the new release at: https://dev.to/kataras/iris-version-11-2-released-22bc
+Read about the new release at: https://www.facebook.com/iris.framework/posts/3276606095684693
diff --git a/README.md b/README.md
index 05985b155f..42cf09976d 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,9 @@ Iris is a fast, simple yet fully featured and very efficient web framework for G
Learn what [others say about Iris](https://iris-go.com/testimonials/) and **star** this github repository.
-> Version 11.2 **released!** [Spread the news](https://dev.to/kataras/iris-version-11-2-released-22bc).
+> Version 11.2 **released!**
+
+[![https://www.facebook.com/iris.framework/posts/3276606095684693](https://iris-go.com/images/iris-112-released.png)](https://www.facebook.com/iris.framework/posts/3276606095684693)
## Learning Iris
@@ -53,9 +55,10 @@ For a more detailed technical documentation you can head over to our [godocs](ht
### Do you like to read while traveling?
+
+
You can [request](https://bit.ly/iris-req-book) a PDF version and online access of the **E-Book** today and be participated in the development of Iris.
-[![https://iris-go.com/images/iris-book-overview.png](https://iris-go.com/images/iris-book-overview.png)](https://bit.ly/iris-req-book)
## Contributing
diff --git a/README_ZH.md b/README_ZH.md
index f72149901f..bdfb98f3ed 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -8,7 +8,7 @@ Iris 是基于 Go 编写的一个快速,简单但功能齐全且非常高效
看看 [其他人如何评价 Iris](https://iris-go.com/testimonials/),同时欢迎各位点亮 **star**。
-> 新版本 11.2 发布! [散布消息](https://dev.to/kataras/iris-version-11-2-released-22bc).
+> 新版本 11.2 发布! [散布消息](https://www.facebook.com/iris.framework/posts/3276606095684693).
## 学习 Iris
diff --git a/VERSION b/VERSION
index 1608649b8e..ead23276fc 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-11.2.1:https://dev.to/kataras/iris-version-11-2-released-22bc
\ No newline at end of file
+11.2.3:https://github.com/kataras/iris/releases/tag/v11.2.3
\ No newline at end of file
diff --git a/_examples/README.md b/_examples/README.md
index f81d887321..8b4c742e0c 100644
--- a/_examples/README.md
+++ b/_examples/README.md
@@ -224,6 +224,7 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her
- [Read JSON](http_request/read-json/main.go)
* [Struct Validation](http_request/read-json-struct-validation/main.go)
- [Read XML](http_request/read-xml/main.go)
+- [Read YAML](http_request/read-yaml/main.go) **NEW**
- [Read Form](http_request/read-form/main.go)
- [Read Query](http_request/read-query/main.go) **NEW**
- [Read Custom per type](http_request/read-custom-per-type/main.go)
@@ -237,6 +238,7 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her
### How to Write to `context.ResponseWriter() http.ResponseWriter`
+- [Content Negotiation](http_responsewriter/content-negotiation) **NEW**
- [Write `valyala/quicktemplate` templates](http_responsewriter/quicktemplate)
- [Write `shiyanhui/hero` templates](http_responsewriter/herotemplate)
- [Text, Markdown, HTML, JSON, JSONP, XML, Binary](http_responsewriter/write-rest/main.go)
diff --git a/_examples/README_ZH.md b/_examples/README_ZH.md
index 98e927cf1f..8f86ac9f1c 100644
--- a/_examples/README_ZH.md
+++ b/_examples/README_ZH.md
@@ -330,6 +330,7 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her
- [读取JSON](http_request/read-json/main.go)
- [读取XML](http_request/read-xml/main.go)
+- [读取YAML](http_request/read-yaml/main.go) **更新**
- [读取Form](http_request/read-form/main.go)
- [读取Query](http_request/read-query/main.go) **更新**
- [读取每个类型的自定义结果Custom per type](http_request/read-custom-per-type/main.go)
@@ -342,6 +343,7 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her
### 如何写入`context.ResponseWriter() http.ResponseWriter`
+- [Content Negotiation](http_responsewriter/content-negotiation) **更新**
- [`valyala/quicktemplate`模版](http_responsewriter/quicktemplate)
- [`shiyanhui/hero`模版](http_responsewriter/herotemplate)
- [Text, Markdown, HTML, JSON, JSONP, XML, Binary](http_responsewriter/write-rest/main.go)
diff --git a/_examples/http_request/read-yaml/main.go b/_examples/http_request/read-yaml/main.go
new file mode 100644
index 0000000000..f0098a1e9d
--- /dev/null
+++ b/_examples/http_request/read-yaml/main.go
@@ -0,0 +1,36 @@
+package main
+
+import (
+ "github.com/kataras/iris"
+)
+
+func newApp() *iris.Application {
+ app := iris.New()
+ app.Post("/", handler)
+
+ return app
+}
+
+// simple yaml stuff, read more at https://yaml.org/start.html
+type product struct {
+ Invoice int `yaml:"invoice"`
+ Tax float32 `yaml:"tax"`
+ Total float32 `yaml:"total"`
+ Comments string `yaml:"comments"`
+}
+
+func handler(ctx iris.Context) {
+ var p product
+ if err := ctx.ReadYAML(&p); err != nil {
+ ctx.StatusCode(iris.StatusBadRequest)
+ ctx.WriteString(err.Error())
+ return
+ }
+
+ ctx.Writef("Received: %#+v", p)
+}
+
+func main() {
+ app := newApp()
+ app.Run(iris.Addr(":8080"))
+}
diff --git a/_examples/http_request/read-yaml/main_test.go b/_examples/http_request/read-yaml/main_test.go
new file mode 100644
index 0000000000..ab6b13a628
--- /dev/null
+++ b/_examples/http_request/read-yaml/main_test.go
@@ -0,0 +1,24 @@
+package main
+
+import (
+ "testing"
+
+ "github.com/kataras/iris/httptest"
+)
+
+func TestReadYAML(t *testing.T) {
+ app := newApp()
+ e := httptest.New(t, app)
+
+ expectedResponse := `Received: main.product{Invoice:34843, Tax:251.42, Total:4443.52, Comments:"Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338."}`
+ send := `invoice: 34843
+tax : 251.42
+total: 4443.52
+comments: >
+ Late afternoon is best.
+ Backup contact is Nancy
+ Billsmer @ 338-4338.`
+
+ e.POST("/").WithHeader("Content-Type", "application/x-yaml").WithBytes([]byte(send)).Expect().
+ Status(httptest.StatusOK).Body().Equal(expectedResponse)
+}
diff --git a/_examples/http_responsewriter/content-negotiation/main.go b/_examples/http_responsewriter/content-negotiation/main.go
new file mode 100644
index 0000000000..7a61dbd044
--- /dev/null
+++ b/_examples/http_responsewriter/content-negotiation/main.go
@@ -0,0 +1,114 @@
+// Package main contains three different ways to render content based on the client's accepted.
+package main
+
+import "github.com/kataras/iris"
+
+type testdata struct {
+ Name string `json:"name" xml:"Name"`
+ Age int `json:"age" xml:"Age"`
+}
+
+func newApp() *iris.Application {
+ app := iris.New()
+ app.Logger().SetLevel("debug")
+
+ // app.Use(func(ctx iris.Context) {
+ // requestedMime := ctx.URLParamDefault("type", "application/json")
+ //
+ // ctx.Negotiation().Accept.Override().MIME(requestedMime, nil)
+ // ctx.Next()
+ // })
+
+ app.Get("/resource", func(ctx iris.Context) {
+ data := testdata{
+ Name: "test name",
+ Age: 26,
+ }
+
+ // Server allows response only JSON and XML. These values
+ // are compared with the clients mime needs. Iris comes with default mime types responses
+ // but you can add a custom one by the `Negotiation().Mime(mime, content)` method,
+ // same for the "accept".
+ // You can also pass a custom ContentSelector(mime string) or ContentNegotiator to the
+ // `Context.Negotiate` method if you want to perform more advanced things.
+ //
+ //
+ // By-default the client accept mime is retrieved by the "Accept" header
+ // Indeed you can override or update it by `Negotiation().Accept.XXX` i.e
+ // ctx.Negotiation().Accept.Override().XML()
+ //
+ // All these values can change inside middlewares, the `Negotiation().Override()` and `.Accept.Override()`
+ // can override any previously set values.
+ // Order matters, if the client accepts anything (*/*)
+ // then the first prioritized mime's response data will be rendered.
+ ctx.Negotiation().JSON().XML()
+ // Accept-Charset vs:
+ ctx.Negotiation().Charset("utf-8", "iso-8859-7")
+ // Alternatively you can define the content/data per mime type
+ // anywhere in the handlers chain using the optional "v" variadic
+ // input argument of the Context.Negotiation().JSON,XML,YAML,Binary,Text,HTML(...) and e.t.c
+ // example (order matters):
+ // ctx.Negotiation().JSON(data).XML(data).Any("content for */*")
+ // ctx.Negotiate(nil)
+
+ // if not nil passed in the `Context.Negotiate` method
+ // then it overrides any contents made by the negotitation builder above.
+ _, err := ctx.Negotiate(data)
+ if err != nil {
+ ctx.Writef("%v", err)
+ }
+ })
+
+ app.Get("/resource2", func(ctx iris.Context) {
+ jsonAndXML := testdata{
+ Name: "test name",
+ Age: 26,
+ }
+
+ // I prefer that one, as it gives me the freedom to modify
+ // response data per accepted mime content type on middlewares as well.
+ ctx.Negotiation().
+ JSON(jsonAndXML).
+ XML(jsonAndXML).
+ HTML("
Test Name
Age 26
")
+
+ ctx.Negotiate(nil)
+ })
+
+ app.Get("/resource3", func(ctx iris.Context) {
+ // If that line is missing and the requested
+ // mime type of content is */* or application/xml or application/json
+ // then 406 Not Acceptable http error code will be rendered instead.
+ //
+ // We also add the "gzip" algorithm as an option to encode
+ // resources on send.
+ ctx.Negotiation().JSON().XML().HTML().EncodingGzip()
+
+ jsonAndXML := testdata{
+ Name: "test name",
+ Age: 26,
+ }
+
+ // Prefer that way instead of the '/resource2' above
+ // if "iris.N" is a static one and can be declared
+ // outside of a handler.
+ ctx.Negotiate(iris.N{
+ // Text: for text/plain,
+ // Markdown: for text/mardown,
+ // Binary: for application/octet-stream,
+ // YAML: for application/x-yaml,
+ // JSONP: for application/javascript
+ // Other: for anything else,
+ JSON: jsonAndXML, // for application/json
+ XML: jsonAndXML, // for application/xml or text/xml
+ HTML: "Test Name
Age 26
", // for text/html
+ })
+ })
+
+ return app
+}
+
+func main() {
+ app := newApp()
+ app.Run(iris.Addr(":8080"))
+}
diff --git a/_examples/http_responsewriter/content-negotiation/main_test.go b/_examples/http_responsewriter/content-negotiation/main_test.go
new file mode 100644
index 0000000000..0c69a09278
--- /dev/null
+++ b/_examples/http_responsewriter/content-negotiation/main_test.go
@@ -0,0 +1,78 @@
+package main
+
+import (
+ "bytes"
+ "compress/gzip"
+ "encoding/xml"
+ "io/ioutil"
+ "testing"
+
+ "github.com/kataras/iris/httptest"
+)
+
+func TestContentNegotiation(t *testing.T) {
+ var (
+ expectedJSONResponse = testdata{
+ Name: "test name",
+ Age: 26,
+ }
+ expectedXMLResponse, _ = xml.Marshal(expectedJSONResponse)
+ expectedHTMLResponse = "Test Name
Age 26
"
+ )
+
+ e := httptest.New(t, newApp())
+
+ e.GET("/resource").WithHeader("Accept", "application/json").
+ Expect().Status(httptest.StatusOK).
+ ContentType("application/json", "utf-8").
+ JSON().Equal(expectedJSONResponse)
+ e.GET("/resource").WithHeader("Accept", "application/xml").WithHeader("Accept-Charset", "iso-8859-7").
+ Expect().Status(httptest.StatusOK).
+ ContentType("application/xml", "iso-8859-7").
+ Body().Equal(string(expectedXMLResponse))
+
+ e.GET("/resource2").WithHeader("Accept", "application/json").
+ Expect().Status(httptest.StatusOK).
+ ContentType("application/json", "utf-8").
+ JSON().Equal(expectedJSONResponse)
+ e.GET("/resource2").WithHeader("Accept", "application/xml").
+ Expect().Status(httptest.StatusOK).
+ ContentType("application/xml", "utf-8").
+ Body().Equal(string(expectedXMLResponse))
+ e.GET("/resource2").WithHeader("Accept", "text/html").
+ Expect().Status(httptest.StatusOK).
+ ContentType("text/html", "utf-8").
+ Body().Equal(expectedHTMLResponse)
+
+ e.GET("/resource3").WithHeader("Accept", "application/json").
+ Expect().Status(httptest.StatusOK).
+ ContentType("application/json", "utf-8").
+ JSON().Equal(expectedJSONResponse)
+ e.GET("/resource3").WithHeader("Accept", "application/xml").
+ Expect().Status(httptest.StatusOK).
+ ContentType("application/xml", "utf-8").
+ Body().Equal(string(expectedXMLResponse))
+
+ // test html with "gzip" encoding algorithm.
+ rawGzipResponse := e.GET("/resource3").WithHeader("Accept", "text/html").
+ WithHeader("Accept-Encoding", "gzip").
+ Expect().Status(httptest.StatusOK).
+ ContentType("text/html", "utf-8").
+ ContentEncoding("gzip").
+ Body().Raw()
+
+ zr, err := gzip.NewReader(bytes.NewReader([]byte(rawGzipResponse)))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rawResponse, err := ioutil.ReadAll(zr)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if expected, got := expectedHTMLResponse, string(rawResponse); expected != got {
+ t.Fatalf("expected response to be:\n%s but got:\n%s", expected, got)
+ }
+
+}
diff --git a/_examples/miscellaneous/pprof/main.go b/_examples/miscellaneous/pprof/main.go
index 2b2a79e94c..80cb9429e3 100644
--- a/_examples/miscellaneous/pprof/main.go
+++ b/_examples/miscellaneous/pprof/main.go
@@ -13,7 +13,9 @@ func main() {
ctx.HTML(" Please click here")
})
- app.Any("/debug/pprof/{action:path}", pprof.New())
+ p := pprof.New()
+ app.Any("/debug/pprof", p)
+ app.Any("/debug/pprof/{action:path}", p)
// ___________
app.Run(iris.Addr(":8080"))
}
diff --git a/_examples/routing/basic/main.go b/_examples/routing/basic/main.go
index d03ea46c0d..9bb530d9eb 100644
--- a/_examples/routing/basic/main.go
+++ b/_examples/routing/basic/main.go
@@ -4,8 +4,9 @@ import (
"github.com/kataras/iris"
)
-func main() {
+func newApp() *iris.Application {
app := iris.New()
+ app.Logger().SetLevel("debug")
// registers a custom handler for 404 not found http (error) status code,
// fires when route not found or manually by ctx.StatusCode(iris.StatusNotFound).
@@ -17,8 +18,8 @@ func main() {
//
// Third receiver should contains the route's handler(s), they are executed by order.
app.Handle("GET", "/", func(ctx iris.Context) {
- // navigate to the middle of $GOPATH/src/github.com/kataras/iris/context/context.go
- // to overview all context's method (there a lot of them, read that and you will learn how iris works too)
+ // navigate to the https://github.com/kataras/iris/wiki/Routing-context-methods
+ // to overview all context's method.
ctx.HTML("Hello from " + ctx.Path()) // Hello from /
})
@@ -26,7 +27,46 @@ func main() {
ctx.Writef(`Same as app.Handle("GET", "/", [...])`)
})
- app.Get("/donate", donateHandler, donateFinishHandler)
+ // Different path parameters types in the same path.
+ app.Get("/u/{p:path}", func(ctx iris.Context) {
+ ctx.Writef(":string, :int, :uint, :alphabetical and :path in the same path pattern.")
+ })
+
+ app.Get("/u/{username:string}", func(ctx iris.Context) {
+ ctx.Writef("before username (string), current route name: %s\n", ctx.RouteName())
+ ctx.Next()
+ }, func(ctx iris.Context) {
+ ctx.Writef("username (string): %s", ctx.Params().Get("username"))
+ })
+
+ app.Get("/u/{id:int}", func(ctx iris.Context) {
+ ctx.Writef("before id (int), current route name: %s\n", ctx.RouteName())
+ ctx.Next()
+ }, func(ctx iris.Context) {
+ ctx.Writef("id (int): %d", ctx.Params().GetIntDefault("id", 0))
+ })
+
+ app.Get("/u/{uid:uint}", func(ctx iris.Context) {
+ ctx.Writef("before uid (uint), current route name: %s\n", ctx.RouteName())
+ ctx.Next()
+ }, func(ctx iris.Context) {
+ ctx.Writef("uid (uint): %d", ctx.Params().GetUintDefault("uid", 0))
+ })
+
+ app.Get("/u/{firstname:alphabetical}", func(ctx iris.Context) {
+ ctx.Writef("before firstname (alphabetical), current route name: %s\n", ctx.RouteName())
+ ctx.Next()
+ }, func(ctx iris.Context) {
+ ctx.Writef("firstname (alphabetical): %s", ctx.Params().Get("firstname"))
+ })
+
+ /*
+ /u/some/path/here maps to :path
+ /u/abcd maps to :alphabetical (if :alphabetical registered otherwise :string)
+ /u/42 maps to :uint (if :uint registered otherwise :int)
+ /u/-1 maps to :int (if :int registered otherwise :string)
+ /u/abcd123 maps to :string
+ */
// Pssst, don't forget dynamic-path example for more "magic"!
app.Get("/api/users/{userid:uint64 min(1)}", func(ctx iris.Context) {
@@ -92,8 +132,9 @@ func main() {
{ // braces are optional, it's just type of style, to group the routes visually.
// http://v1.localhost:8080
+ // Note: for versioning-specific features checkout the _examples/versioning instead.
v1.Get("/", func(ctx iris.Context) {
- ctx.HTML("Version 1 API. go to /api/users")
+ ctx.HTML(`Version 1 API. go to /api/users`)
})
usersAPI := v1.Party("/api/users")
@@ -117,9 +158,14 @@ func main() {
})
}
+ return app
+}
+
+func main() {
+ app := newApp()
+
// http://localhost:8080
// http://localhost:8080/home
- // http://localhost:8080/donate
// http://localhost:8080/api/users/42
// http://localhost:8080/admin
// http://localhost:8080/admin/login
@@ -128,6 +174,12 @@ func main() {
// http://localhost:8080/api/users/blabla
// http://localhost:8080/wontfound
//
+ // http://localhost:8080/u/abcd
+ // http://localhost:8080/u/42
+ // http://localhost:8080/u/-1
+ // http://localhost:8080/u/abcd123
+ // http://localhost:8080/u/some/path/here
+ //
// if hosts edited:
// http://v1.localhost:8080
// http://v1.localhost:8080/api/users
@@ -141,24 +193,6 @@ func adminMiddleware(ctx iris.Context) {
ctx.Next() // to move to the next handler, or don't that if you have any auth logic.
}
-func donateHandler(ctx iris.Context) {
- ctx.Writef("Just like an inline handler, but it can be " +
- "used by other package, anywhere in your project.")
-
- // let's pass a value to the next handler
- // Values is the way handlers(or middleware) are communicating between each other.
- ctx.Values().Set("donate_url", "https://github.com/kataras/iris#-people")
- ctx.Next() // in order to execute the next handler in the chain, look donate route.
-}
-
-func donateFinishHandler(ctx iris.Context) {
- // values can be any type of object so we could cast the value to a string
- // but iris provides an easy to do that, if donate_url is not defined, then it returns an empty string instead.
- donateURL := ctx.Values().GetString("donate_url")
- ctx.Application().Logger().Infof("donate_url value was: " + donateURL)
- ctx.Writef("\n\nDonate sent(?).")
-}
-
func notFoundHandler(ctx iris.Context) {
ctx.HTML("Custom route for 404 not found http code, here you can render a view, html, json any valid response.")
}
diff --git a/_examples/routing/basic/main_test.go b/_examples/routing/basic/main_test.go
new file mode 100644
index 0000000000..0f1b98cd2f
--- /dev/null
+++ b/_examples/routing/basic/main_test.go
@@ -0,0 +1,89 @@
+package main
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/kataras/iris/httptest"
+)
+
+// Shows a very basic usage of the httptest.
+// The tests are written in a way to be easy to understand,
+// for a more comprehensive testing examples check out the:
+// _examples/routing/main_test.go,
+// _examples/subdomains/www/main_test.go
+// _examples/file-server and e.t.c.
+// Almost every example which covers
+// a new feature from you to learn
+// contains a test file as well.
+func TestRoutingBasic(t *testing.T) {
+ expectedUResponse := func(paramName, paramType, paramValue string) string {
+ s := fmt.Sprintf("before %s (%s), current route name: GET/u/{%s:%s}\n", paramName, paramType, paramName, paramType)
+ s += fmt.Sprintf("%s (%s): %s", paramName, paramType, paramValue)
+ return s
+ }
+
+ var (
+ expectedNotFoundResponse = "Custom route for 404 not found http code, here you can render a view, html, json any valid response."
+
+ expectedIndexResponse = "Hello from /"
+ expectedHomeResponse = `Same as app.Handle("GET", "/", [...])`
+
+ expectedUpathResponse = ":string, :int, :uint, :alphabetical and :path in the same path pattern."
+ expectedUStringResponse = expectedUResponse("username", "string", "abcd123")
+ expectedUIntResponse = expectedUResponse("id", "int", "-1")
+ expectedUUintResponse = expectedUResponse("uid", "uint", "42")
+ expectedUAlphabeticalResponse = expectedUResponse("firstname", "alphabetical", "abcd")
+
+ expectedAPIUsersIndexResponse = map[string]interface{}{"user_id": 42}
+
+ expectedAdminIndexResponse = "Hello from admin/
"
+
+ expectedSubdomainV1IndexResponse = `Version 1 API. go to /api/users`
+ expectedSubdomainV1APIUsersIndexResponse = "All users"
+ expectedSubdomainV1APIUsersIndexWithParamResponse = "user with id: 42"
+
+ expectedSubdomainWildcardIndexResponse = "Subdomain can be anything, now you're here from: any-subdomain-here"
+ )
+
+ app := newApp()
+ e := httptest.New(t, app)
+
+ e.GET("/anotfound").Expect().Status(httptest.StatusNotFound).
+ Body().Equal(expectedNotFoundResponse)
+
+ e.GET("/").Expect().Status(httptest.StatusOK).
+ Body().Equal(expectedIndexResponse)
+ e.GET("/home").Expect().Status(httptest.StatusOK).
+ Body().Equal(expectedHomeResponse)
+
+ e.GET("/u/some/path/here").Expect().Status(httptest.StatusOK).
+ Body().Equal(expectedUpathResponse)
+ e.GET("/u/abcd123").Expect().Status(httptest.StatusOK).
+ Body().Equal(expectedUStringResponse)
+ e.GET("/u/-1").Expect().Status(httptest.StatusOK).
+ Body().Equal(expectedUIntResponse)
+ e.GET("/u/42").Expect().Status(httptest.StatusOK).
+ Body().Equal(expectedUUintResponse)
+ e.GET("/u/abcd").Expect().Status(httptest.StatusOK).
+ Body().Equal(expectedUAlphabeticalResponse)
+
+ e.GET("/api/users/42").Expect().Status(httptest.StatusOK).
+ JSON().Equal(expectedAPIUsersIndexResponse)
+
+ e.GET("/admin").Expect().Status(httptest.StatusOK).
+ Body().Equal(expectedAdminIndexResponse)
+
+ e.Request("GET", "/").WithURL("http://v1.example.com").Expect().Status(httptest.StatusOK).
+ Body().Equal(expectedSubdomainV1IndexResponse)
+
+ e.Request("GET", "/api/users").WithURL("http://v1.example.com").Expect().Status(httptest.StatusOK).
+ Body().Equal(expectedSubdomainV1APIUsersIndexResponse)
+
+ e.Request("GET", "/api/users/42").WithURL("http://v1.example.com").Expect().Status(httptest.StatusOK).
+ Body().Equal(expectedSubdomainV1APIUsersIndexWithParamResponse)
+
+ e.Request("GET", "/").WithURL("http://any-subdomain-here.example.com").Expect().Status(httptest.StatusOK).
+ Body().Equal(expectedSubdomainWildcardIndexResponse)
+
+}
diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go
index b1ac1510e2..a5f0061821 100644
--- a/_examples/routing/dynamic-path/main.go
+++ b/_examples/routing/dynamic-path/main.go
@@ -284,5 +284,10 @@ func main() {
// Last, do not confuse `ctx.Params()` with `ctx.Values()`.
// Path parameter's values can be retrieved from `ctx.Params()`,
// context's local storage that can be used to communicate between handlers and middleware(s) can be stored to `ctx.Values()`.
+ //
+ // When registering different parameter types in the same exact path pattern, the path parameter's name
+ // should differ e.g.
+ // /path/{name:string}
+ // /path/{id:uint}
app.Run(iris.Addr(":8080"))
}
diff --git a/_examples/routing/main.go b/_examples/routing/main.go
index 9f4503274e..ccd35c9e7c 100644
--- a/_examples/routing/main.go
+++ b/_examples/routing/main.go
@@ -1,8 +1,6 @@
package main
import (
- "io/ioutil"
-
"github.com/kataras/iris"
)
@@ -108,7 +106,7 @@ func newApp() *iris.Application {
// to protect ourselves from "over heating".
app.Post("/", iris.LimitRequestBodySize(maxBodySize), func(ctx iris.Context) {
// get request body
- b, err := ioutil.ReadAll(ctx.Request().Body)
+ b, err := ctx.GetBody()
// if is larger then send a bad request status
if err != nil {
ctx.StatusCode(iris.StatusBadRequest)
diff --git a/_examples/sessions/database/redis/main.go b/_examples/sessions/database/redis/main.go
index e48f736f86..1effb9adc6 100644
--- a/_examples/sessions/database/redis/main.go
+++ b/_examples/sessions/database/redis/main.go
@@ -21,6 +21,7 @@ func main() {
Password: "",
Database: "",
Prefix: "",
+ Delim: "-",
}) // optionally configure the bridge between your redis server.
// close connection when control+C/cmd+C
diff --git a/_examples/websocket/basic/browser/index.html b/_examples/websocket/basic/browser/index.html
index 836ef22362..493b79875e 100644
--- a/_examples/websocket/basic/browser/index.html
+++ b/_examples/websocket/basic/browser/index.html
@@ -53,6 +53,8 @@
};
}
+ const username = window.prompt("Your username?");
+
async function runExample() {
// You can omit the "default" and simply define only Events, the namespace will be an empty string"",
// however if you decide to make any changes on this example make sure the changes are reflecting inside the ../server.go file as well.
@@ -70,6 +72,10 @@
addMessage(msg.Body);
}
}
+ },{
+ headers: {
+ "X-Username": username,
+ }
});
// You can either wait to conenct or just conn.connect("connect")
diff --git a/_examples/websocket/basic/go-client/client.go b/_examples/websocket/basic/go-client/client.go
index edfff93620..80a68ecfbc 100644
--- a/_examples/websocket/basic/go-client/client.go
+++ b/_examples/websocket/basic/go-client/client.go
@@ -42,7 +42,10 @@ func main() {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(dialAndConnectTimeout))
defer cancel()
- client, err := websocket.Dial(ctx, websocket.DefaultGorillaDialer, endpoint, clientEvents)
+ // username := "my_username"
+ // dialer := websocket.GobwasDialer(websocket.GobwasDialerOptions{Header: websocket.GobwasHeader{"X-Username": []string{username}}})
+ dialer := websocket.DefaultGobwasDialer
+ client, err := websocket.Dial(ctx, dialer, endpoint, clientEvents)
if err != nil {
panic(err)
}
diff --git a/_examples/websocket/basic/server.go b/_examples/websocket/basic/server.go
index f71a49a680..a333ce0b4a 100644
--- a/_examples/websocket/basic/server.go
+++ b/_examples/websocket/basic/server.go
@@ -68,8 +68,17 @@ func main() {
SigningMethod: jwt.SigningMethodHS256,
})
+ idGen := func(ctx iris.Context) string {
+ if username := ctx.GetHeader("X-Username"); username != "" {
+ return username
+ }
+
+ return websocket.DefaultIDGenerator(ctx)
+ }
+
// serves the endpoint of ws://localhost:8080/echo
- websocketRoute := app.Get("/echo", websocket.Handler(websocketServer))
+ // with optional custom ID generator.
+ websocketRoute := app.Get("/echo", websocket.Handler(websocketServer, idGen))
if enableJWT {
// Register the jwt middleware (on handshake):
diff --git a/context/context.go b/context/context.go
index 11eb036481..c334f08c20 100644
--- a/context/context.go
+++ b/context/context.go
@@ -586,6 +586,10 @@ type Context interface {
//
// Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go
ReadXML(xmlObjectPtr interface{}) error
+ // ReadYAML reads YAML from request's body and binds it to the "outPtr" value.
+ //
+ // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-yaml/main.go
+ ReadYAML(outPtr interface{}) error
// ReadForm binds the formObject with the form data
// it supports any kind of type, including custom structs.
// It will return nothing if request data are empty.
@@ -781,6 +785,49 @@ type Context interface {
Markdown(markdownB []byte, options ...Markdown) (int, error)
// YAML parses the "v" using the yaml parser and renders its result to the client.
YAML(v interface{}) (int, error)
+
+ // +-----------------------------------------------------------------------+
+ // | Content Νegotiation |
+ // | https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation | |
+ // +-----------------------------------------------------------------------+
+
+ // Negotiation creates once and returns the negotiation builder
+ // to build server-side available content for specific mime type(s)
+ // and charset(s).
+ //
+ // See `Negotiate` method too.
+ Negotiation() *NegotiationBuilder
+ // Negotiate used for serving different representations of a resource at the same URI.
+ //
+ // The "v" can be a single `N` struct value.
+ // The "v" can be any value completes the `ContentSelector` interface.
+ // The "v" can be any value completes the `ContentNegotiator` interface.
+ // The "v" can be any value of struct(JSON, JSONP, XML, YAML) or
+ // string(TEXT, HTML) or []byte(Markdown, Binary) or []byte with any matched mime type.
+ //
+ // If the "v" is nil, the `Context.Negotitation()` builder's
+ // content will be used instead, otherwise "v" overrides builder's content
+ // (server mime types are still retrieved by its registered, supported, mime list)
+ //
+ // Set mime type priorities by `Negotiation().JSON().XML().HTML()...`.
+ // Set charset priorities by `Negotiation().Charset(...)`.
+ // Set encoding algorithm priorities by `Negotiation().Encoding(...)`.
+ // Modify the accepted by
+ // `Negotiation().Accept./Override()/.XML().JSON().Charset(...).Encoding(...)...`.
+ //
+ // It returns `ErrContentNotSupported` when not matched mime type(s).
+ //
+ // Resources:
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding
+ //
+ // Supports the above without quality values.
+ //
+ // Read more at: https://github.com/kataras/iris/wiki/Content-negotiation
+ Negotiate(v interface{}) (int, error)
+
// +------------------------------------------------------------+
// | Serve files |
// +------------------------------------------------------------+
@@ -1750,7 +1797,9 @@ func (ctx *context) contentTypeOnce(cType string, charset string) {
charset = ctx.Application().ConfigurationReadOnly().GetCharset()
}
- cType += "; charset=" + charset
+ if cType != ContentBinaryHeaderValue {
+ cType += "; charset=" + charset
+ }
ctx.Values().Set(contentTypeContextKey, cType)
ctx.writer.Header().Set(ContentTypeHeaderKey, cType)
@@ -2396,19 +2445,26 @@ func (ctx *context) shouldOptimize() bool {
// ReadJSON reads JSON from request's body and binds it to a value of any json-valid type.
//
// Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-json/main.go
-func (ctx *context) ReadJSON(jsonObject interface{}) error {
+func (ctx *context) ReadJSON(outPtr interface{}) error {
var unmarshaler = json.Unmarshal
if ctx.shouldOptimize() {
unmarshaler = jsoniter.Unmarshal
}
- return ctx.UnmarshalBody(jsonObject, UnmarshalerFunc(unmarshaler))
+ return ctx.UnmarshalBody(outPtr, UnmarshalerFunc(unmarshaler))
}
// ReadXML reads XML from request's body and binds it to a value of any xml-valid type.
//
// Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go
-func (ctx *context) ReadXML(xmlObject interface{}) error {
- return ctx.UnmarshalBody(xmlObject, UnmarshalerFunc(xml.Unmarshal))
+func (ctx *context) ReadXML(outPtr interface{}) error {
+ return ctx.UnmarshalBody(outPtr, UnmarshalerFunc(xml.Unmarshal))
+}
+
+// ReadYAML reads YAML from request's body and binds it to the "outPtr" value.
+//
+// Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-yaml/main.go
+func (ctx *context) ReadYAML(outPtr interface{}) error {
+ return ctx.UnmarshalBody(outPtr, UnmarshalerFunc(yaml.Unmarshal))
}
// IsErrPath can be used at `context#ReadForm` and `context#ReadQuery`.
@@ -2893,126 +2949,6 @@ const (
ContentFormMultipartHeaderValue = "multipart/form-data"
)
-// TODO:
-// const negotitationContextKey = "_iris_accept_negotitation_builder"
-
-// func (ctx *context) Accept() *Negotitation {
-// if n := ctx.Values().Get(negotitationContextKey); n != nil {
-// return n.(*Negotitation)
-// }
-
-// n := new(Negotitation)
-// n.accept = parseHeader(ctx.GetHeader("Accept"))
-// n.charset = parseHeader(ctx.GetHeader("Accept-Charset"))
-
-// ctx.Values().Set(negotitationContextKey, n)
-// return n
-// }
-
-// func parseHeader(headerValue string) []string {
-// in := strings.Split(headerValue, ",")
-// out := make([]string, 0, len(in))
-
-// for _, value := range in {
-// // remove any spaces and quality values such as ;q=0.8.
-// // */* or * means accept everything.
-// v := strings.TrimSpace(strings.Split(value, ";")[0])
-// if v != "" {
-// out = append(out, v)
-// }
-// }
-
-// return out
-// }
-
-// // Negotitation builds the accepted mime types and charset
-// //
-// // and "Accept-Charset" headers respectfully.
-// // The default values are set by the client side, server can append or override those.
-// // The end result will be challenged with runtime preffered set of content types and charsets.
-// //
-// // See `Negotitate`.
-// type Negotitation struct {
-// // initialized with "Accept" header values.
-// accept []string
-// // initialized with "Accept-Charset" and if was empty then the
-// // application's default (which defaults to utf-8).
-// charset []string
-
-// // To support override in request life cycle.
-// // We need slice when data is the same format
-// // for one or more mime types,
-// // i.e text/xml and obselete application/xml.
-// lastAccept []string
-// lastCharset []string
-// }
-
-// func (n *Negotitation) Override() *Negotitation {
-// // when called first.
-// n.accept = n.accept[0:0]
-// n.charset = n.charset[0:0]
-
-// // when called after.
-// if len(n.lastAccept) > 0 {
-// n.accept = append(n.accept, n.lastAccept...)
-// n.lastAccept = n.lastAccept[0:0]
-// }
-
-// if len(n.lastCharset) > 0 {
-// n.charset = append(n.charset, n.lastCharset...)
-// n.lastCharset = n.lastCharset[0:0]
-// }
-
-// return n
-// }
-
-// func (n *Negotitation) MIME(mimeType ...string) *Negotitation {
-// n.lastAccept = mimeType
-// n.accept = append(n.accept, mimeType...)
-// return n
-// }
-
-// func (n *Negotitation) JSON() *Negotitation {
-// return n.MIME(ContentJSONHeaderValue)
-// }
-
-// func (n *Negotitation) XML() *Negotitation {
-// return n.MIME(ContentXMLHeaderValue, ContentXMLUnreadableHeaderValue)
-// }
-
-// func (n *Negotitation) HTML() *Negotitation {
-// return n.MIME(ContentHTMLHeaderValue)
-// }
-
-// func (n *Negotitation) Charset(charset ...string) *Negotitation {
-// n.lastCharset = charset
-// n.charset = append(n.charset, charset...)
-
-// return n
-// }
-
-// func (n *Negotitation) build(preferences []string) (contentType, charset string) {
-// return
-// }
-
-// // https://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html
-// // https://developer.mozilla.org/en-US/docs/tag/Content%20Negotiation
-// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
-// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values
-// func (ctx *context) Negotiate(v interface{}, preferences ...string) (int, error) {
-// contentType, charset := ctx.Accept().build(preferences)
-
-// // // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset
-// // If the server cannot serve any matching character set,
-// // it can theoretically send back a 406 (Not Acceptable) error code.
-// ctx.contentTypeOnce(contentType, charset)
-
-// switch contentType {
-
-// }
-// return -1, nil
-// }
-
// Binary writes out the raw bytes as binary data.
func (ctx *context) Binary(data []byte) (int, error) {
ctx.ContentType(ContentBinaryHeaderValue)
@@ -3319,6 +3255,627 @@ func (ctx *context) YAML(v interface{}) (int, error) {
return ctx.Write(out)
}
+// +-----------------------------------------------------------------------+
+// | Content Νegotiation |
+// | https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation | |
+// +-----------------------------------------------------------------------+
+
+// ErrContentNotSupported returns from the `Negotiate` method
+// when server responds with 406.
+var ErrContentNotSupported = errors.New("unsupported content")
+
+// ContentSelector is the interface which structs can implement
+// to manually choose a content based on the negotiated mime (content type).
+// It can be passed to the `Context.Negotiate` method.
+//
+// See the `N` struct too.
+type ContentSelector interface {
+ SelectContent(mime string) interface{}
+}
+
+// ContentNegotiator is the interface which structs can implement
+// to override the `Context.Negotiate` default implementation and
+// manually respond to the client based on a manuall call of `Context.Negotiation().Build()`
+// to get the final negotiated mime and charset.
+// It can be passed to the `Context.Negotiate` method.
+type ContentNegotiator interface {
+ // mime and charset can be retrieved by:
+ // mime, charset := Context.Negotiation().Build()
+ // Pass this method to `Context.Negotiate` method
+ // to write custom content.
+ // Overriding the existing behavior of Context.Negotiate for selecting values based on
+ // content types, although it can accept any custom mime type with []byte.
+ // Content type is already set.
+ // Use it with caution, 99.9% you don't need this but it's here for extreme cases.
+ Negotiate(ctx Context) (int, error)
+}
+
+// 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()`
+// server side values. If no matched mime then its "Other" field will be sent,
+// which should be a string or []byte.
+// It completes the `ContentSelector` interface.
+type N struct {
+ Text, HTML string
+ Markdown []byte
+ Binary []byte
+
+ JSON interface{}
+ JSONP interface{}
+ XML interface{}
+ YAML interface{}
+
+ Other []byte // custom content types.
+}
+
+// SelectContent returns a content based on the matched negotiated "mime".
+func (n N) SelectContent(mime string) interface{} {
+ switch mime {
+ case ContentTextHeaderValue:
+ return n.Text
+ case ContentHTMLHeaderValue:
+ return n.HTML
+ case ContentMarkdownHeaderValue:
+ return n.Markdown
+ case ContentBinaryHeaderValue:
+ return n.Binary
+ case ContentJSONHeaderValue:
+ return n.JSON
+ case ContentJavascriptHeaderValue:
+ return n.JSONP
+ case ContentXMLHeaderValue, ContentXMLUnreadableHeaderValue:
+ return n.XML
+ case ContentYAMLHeaderValue:
+ return n.YAML
+ default:
+ return n.Other
+ }
+}
+
+const negotiationContextKey = "_iris_negotiation_builder"
+
+// Negotiation creates once and returns the negotiation builder
+// to build server-side available prioritized content
+// for specific content type(s), charset(s) and encoding algorithm(s).
+//
+// See `Negotiate` method too.
+func (ctx *context) Negotiation() *NegotiationBuilder {
+ if n := ctx.Values().Get(negotiationContextKey); n != nil {
+ return n.(*NegotiationBuilder)
+ }
+
+ acceptBuilder := NegotiationAcceptBuilder{}
+ acceptBuilder.accept = parseHeader(ctx.GetHeader("Accept"))
+ acceptBuilder.charset = parseHeader(ctx.GetHeader("Accept-Charset"))
+
+ n := &NegotiationBuilder{Accept: acceptBuilder}
+
+ ctx.Values().Set(negotiationContextKey, n)
+
+ return n
+}
+
+func parseHeader(headerValue string) []string {
+ in := strings.Split(headerValue, ",")
+ out := make([]string, 0, len(in))
+
+ for _, value := range in {
+ // remove any spaces and quality values such as ;q=0.8.
+ v := strings.TrimSpace(strings.Split(value, ";")[0])
+ if v != "" {
+ out = append(out, v)
+ }
+ }
+
+ return out
+}
+
+// Negotiate used for serving different representations of a resource at the same URI.
+//
+// The "v" can be a single `N` struct value.
+// The "v" can be any value completes the `ContentSelector` interface.
+// The "v" can be any value completes the `ContentNegotiator` interface.
+// The "v" can be any value of struct(JSON, JSONP, XML, YAML) or
+// string(TEXT, HTML) or []byte(Markdown, Binary) or []byte with any matched mime type.
+//
+// If the "v" is nil, the `Context.Negotitation()` builder's
+// content will be used instead, otherwise "v" overrides builder's content
+// (server mime types are still retrieved by its registered, supported, mime list)
+//
+// Set mime type priorities by `Negotiation().JSON().XML().HTML()...`.
+// Set charset priorities by `Negotiation().Charset(...)`.
+// Set encoding algorithm priorities by `Negotiation().Encoding(...)`.
+// Modify the accepted by
+// `Negotiation().Accept./Override()/.XML().JSON().Charset(...).Encoding(...)...`.
+//
+// It returns `ErrContentNotSupported` when not matched mime type(s).
+//
+// Resources:
+// https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation
+// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
+// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset
+// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding
+//
+// Supports the above without quality values.
+//
+// Read more at: https://github.com/kataras/iris/wiki/Content-negotiation
+func (ctx *context) Negotiate(v interface{}) (int, error) {
+ contentType, charset, encoding, content := ctx.Negotiation().Build()
+ if v == nil {
+ v = content
+ }
+
+ if contentType == "" {
+ // If the server cannot serve any matching set,
+ // it can send back a 406 (Not Acceptable) error code.
+ ctx.StatusCode(http.StatusNotAcceptable)
+ return -1, ErrContentNotSupported
+ }
+
+ if charset == "" {
+ charset = ctx.Application().ConfigurationReadOnly().GetCharset()
+ }
+
+ if encoding == "gzip" {
+ ctx.Gzip(true)
+ }
+
+ ctx.contentTypeOnce(contentType, charset)
+
+ if n, ok := v.(ContentNegotiator); ok {
+ return n.Negotiate(ctx)
+ }
+
+ if s, ok := v.(ContentSelector); ok {
+ v = s.SelectContent(contentType)
+ }
+
+ // switch v := value.(type) {
+ // case []byte:
+ // if contentType == ContentMarkdownHeaderValue {
+ // return ctx.Markdown(v)
+ // }
+
+ // return ctx.Write(v)
+ // case string:
+ // return ctx.WriteString(v)
+ // default:
+ // make it switch by content-type only, but we lose custom mime types capability that way:
+ // ^ solved with []byte on default case and
+ // ^ N.Other and
+ // ^ ContentSelector and ContentNegotiator interfaces.
+
+ switch contentType {
+ case ContentTextHeaderValue, ContentHTMLHeaderValue:
+ return ctx.WriteString(v.(string))
+ case ContentMarkdownHeaderValue:
+ return ctx.Markdown(v.([]byte))
+ case ContentJSONHeaderValue:
+ return ctx.JSON(v)
+ case ContentJavascriptHeaderValue:
+ return ctx.JSONP(v)
+ case ContentXMLHeaderValue, ContentXMLUnreadableHeaderValue:
+ return ctx.XML(v)
+ case ContentYAMLHeaderValue:
+ return ctx.YAML(v)
+ default:
+ // maybe "Other" or v is []byte or string but not a built-in framework mime,
+ // for custom content types,
+ // panic if not correct usage.
+ switch vv := v.(type) {
+ case []byte:
+ return ctx.Write(vv)
+ case string:
+ return ctx.WriteString(vv)
+ default:
+ ctx.StatusCode(http.StatusNotAcceptable)
+ return -1, ErrContentNotSupported
+ }
+
+ }
+}
+
+// NegotiationBuilder returns from the `Context.Negotitation`
+// and can be used inside chain of handlers to build server-side
+// mime type(s), charset(s) and encoding algorithm(s)
+// that should match with the client's
+// Accept, Accept-Charset and Accept-Encoding headers (by-default).
+// To modify the client's accept use its "Accept" field
+// which it's the `NegotitationAcceptBuilder`.
+//
+// See the `Negotiate` method too.
+type NegotiationBuilder struct {
+ Accept NegotiationAcceptBuilder
+
+ mime []string // we need order.
+ contents map[string]interface{} // map to the "mime" and content should be rendered if that mime requested.
+ charset []string
+ encoding []string
+}
+
+// MIME registers a mime type and optionally the value that should be rendered
+// through `Context.Negotiate` when this mime type is accepted by client.
+//
+// Returns itself for recursive calls.
+func (n *NegotiationBuilder) MIME(mime string, content interface{}) *NegotiationBuilder {
+ mimes := parseHeader(mime) // if contains more than one sep by commas ",".
+ if content == nil {
+ n.mime = append(n.mime, mimes...)
+ return n
+ }
+
+ if n.contents == nil {
+ n.contents = make(map[string]interface{})
+ }
+
+ for _, m := range mimes {
+ n.mime = append(n.mime, m)
+ n.contents[m] = content
+ }
+
+ return n
+}
+
+// Text registers the "text/plain" content type and, optionally,
+// a value that `Context.Negotiate` will render
+// when a client accepts the "text/plain" content type.
+//
+// Returns itself for recursive calls.
+func (n *NegotiationBuilder) Text(v ...string) *NegotiationBuilder {
+ var content interface{}
+ if len(v) > 0 {
+ content = v[0]
+ }
+ return n.MIME(ContentTextHeaderValue, content)
+}
+
+// HTML registers the "text/html" content type and, optionally,
+// a value that `Context.Negotiate` will render
+// when a client accepts the "text/html" content type.
+//
+// Returns itself for recursive calls.
+func (n *NegotiationBuilder) HTML(v ...string) *NegotiationBuilder {
+ var content interface{}
+ if len(v) > 0 {
+ content = v[0]
+ }
+ return n.MIME(ContentHTMLHeaderValue, content)
+}
+
+// Markdown registers the "text/markdown" content type and, optionally,
+// a value that `Context.Negotiate` will render
+// when a client accepts the "text/markdown" content type.
+//
+// Returns itself for recursive calls.
+func (n *NegotiationBuilder) Markdown(v ...[]byte) *NegotiationBuilder {
+ var content interface{}
+ if len(v) > 0 {
+ content = v
+ }
+ return n.MIME(ContentMarkdownHeaderValue, content)
+}
+
+// Binary registers the "application/octet-stream" content type and, optionally,
+// a value that `Context.Negotiate` will render
+// when a client accepts the "application/octet-stream" content type.
+//
+// Returns itself for recursive calls.
+func (n *NegotiationBuilder) Binary(v ...[]byte) *NegotiationBuilder {
+ var content interface{}
+ if len(v) > 0 {
+ content = v[0]
+ }
+ return n.MIME(ContentBinaryHeaderValue, content)
+}
+
+// JSON registers the "application/json" content type and, optionally,
+// a value that `Context.Negotiate` will render
+// when a client accepts the "application/json" content type.
+//
+// Returns itself for recursive calls.
+func (n *NegotiationBuilder) JSON(v ...interface{}) *NegotiationBuilder {
+ var content interface{}
+ if len(v) > 0 {
+ content = v[0]
+ }
+ return n.MIME(ContentJSONHeaderValue, content)
+}
+
+// JSONP registers the "application/javascript" content type and, optionally,
+// a value that `Context.Negotiate` will render
+// when a client accepts the "application/javascript" content type.
+//
+// Returns itself for recursive calls.
+func (n *NegotiationBuilder) JSONP(v ...interface{}) *NegotiationBuilder {
+ var content interface{}
+ if len(v) > 0 {
+ content = v[0]
+ }
+ return n.MIME(ContentJavascriptHeaderValue, content)
+}
+
+// XML registers the "text/xml" and "application/xml" content types and, optionally,
+// a value that `Context.Negotiate` will render
+// when a client accepts one of the "text/xml" or "application/xml" content types.
+//
+// Returns itself for recursive calls.
+func (n *NegotiationBuilder) XML(v ...interface{}) *NegotiationBuilder {
+ var content interface{}
+ if len(v) > 0 {
+ content = v[0]
+ }
+ return n.MIME(ContentXMLHeaderValue+","+ContentXMLUnreadableHeaderValue, content)
+}
+
+// YAML registers the "application/x-yaml" content type and, optionally,
+// a value that `Context.Negotiate` will render
+// when a client accepts the "application/x-yaml" content type.
+//
+// Returns itself for recursive calls.
+func (n *NegotiationBuilder) YAML(v ...interface{}) *NegotiationBuilder {
+ var content interface{}
+ if len(v) > 0 {
+ content = v[0]
+ }
+ return n.MIME(ContentYAMLHeaderValue, content)
+}
+
+// Any registers a wildcard that can match any client's accept content type.
+//
+// Returns itself for recursive calls.
+func (n *NegotiationBuilder) Any(v ...interface{}) *NegotiationBuilder {
+ var content interface{}
+ if len(v) > 0 {
+ content = v[0]
+ }
+ return n.MIME("*", content)
+}
+
+// Charset overrides the application's config's charset (which defaults to "utf-8")
+// that a client should match for
+// (through Accept-Charset header or custom through `NegotitationBuilder.Accept.Override().Charset(...)` call).
+// Do not set it if you don't know what you're doing.
+//
+// Returns itself for recursive calls.
+func (n *NegotiationBuilder) Charset(charset ...string) *NegotiationBuilder {
+ n.charset = append(n.charset, charset...)
+ return n
+}
+
+// Encoding registers one or more encoding algorithms by name, i.e gzip, deflate.
+// that a client should match for (through Accept-Encoding header).
+//
+// Only the "gzip" can be handlded automatically as it's the only builtin encoding algorithm
+// to serve resources.
+//
+// Returns itself for recursive calls.
+func (n *NegotiationBuilder) Encoding(encoding ...string) *NegotiationBuilder {
+ n.encoding = append(n.encoding, encoding...)
+ return n
+}
+
+// EncodingGzip registers the "gzip" encoding algorithm
+// that a client should match for (through Accept-Encoding header or call of Accept.Encoding(enc)).
+//
+// It will make resources to served by "gzip" if Accept-Encoding contains the "gzip" as well.
+//
+// Returns itself for recursive calls.
+func (n *NegotiationBuilder) EncodingGzip() *NegotiationBuilder {
+ return n.Encoding(GzipHeaderValue)
+}
+
+// Build calculates the client's and server's mime type(s), charset(s) and encoding
+// and returns the final content type, charset and encoding that server should render
+// to the client. It does not clear the fields, use the `Clear` method if neeeded.
+//
+// The returned "content" can be nil if the matched "contentType" does not provide any value,
+// in that case the `Context.Negotiate(v)` must be called with a non-nil value.
+func (n *NegotiationBuilder) Build() (contentType, charset, encoding string, content interface{}) {
+ contentType = negotiationMatch(n.Accept.accept, n.mime)
+ charset = negotiationMatch(n.Accept.charset, n.charset)
+ encoding = negotiationMatch(n.Accept.encoding, n.encoding)
+
+ if n.contents != nil {
+ if data, ok := n.contents[contentType]; ok {
+ content = data
+ }
+ }
+
+ return
+}
+
+// Clear clears the prioritized mime type(s), charset(s) and any contents
+// relative to those mime type(s).
+// The "Accept" field is stay as it is, use its `Override` method
+// to clear out the client's accepted mime type(s) and charset(s).
+func (n *NegotiationBuilder) Clear() *NegotiationBuilder {
+ n.mime = n.mime[0:0]
+ n.contents = nil
+ n.charset = n.charset[0:0]
+ return n
+}
+
+func negotiationMatch(in []string, priorities []string) string {
+ // e.g.
+ // match json:
+ // in: text/html, application/json
+ // prioritities: application/json
+ // not match:
+ // in: text/html, application/json
+ // prioritities: text/xml
+ // match html:
+ // in: text/html, application/json
+ // prioritities: */*
+ // not match:
+ // in: application/json
+ // prioritities: text/xml
+ // match json:
+ // in: text/html, application/*
+ // prioritities: application/json
+
+ if len(priorities) == 0 {
+ return ""
+ }
+
+ if len(in) == 0 {
+ return priorities[0]
+ }
+
+ for _, accepted := range in {
+ for _, p := range priorities {
+ // wildcard is */* or text/* and etc.
+ // so loop through each char.
+ for i, n := 0, len(accepted); i < n; i++ {
+ if accepted[i] != p[i] {
+ break
+ }
+
+ if accepted[i] == '*' || p[i] == '*' {
+ return p
+ }
+
+ if i == n-1 {
+ return p
+ }
+ }
+ }
+ }
+
+ return ""
+}
+
+// NegotiationAcceptBuilder builds the accepted mime types and charset
+//
+// and "Accept-Charset" headers respectfully.
+// The default values are set by the client side, server can append or override those.
+// The end result will be challenged with runtime preffered set of content types and charsets.
+//
+// See the `Negotiate` method too.
+type NegotiationAcceptBuilder struct {
+ // initialized with "Accept" request header values.
+ accept []string
+ // initialized with "Accept-Encoding" request header. and if was empty then the
+ // application's default (which defaults to utf-8).
+ charset []string
+ // initialized with "Accept-Encoding" request header values.
+ encoding []string
+
+ // To support override in request life cycle.
+ // We need slice when data is the same format
+ // for one or more mime types,
+ // i.e text/xml and obselete application/xml.
+ lastAccept []string
+ lastCharset []string
+ lastEncoding []string
+}
+
+// Override clears the default values for accept and accept charset.
+// Returns itself.
+func (n *NegotiationAcceptBuilder) Override() *NegotiationAcceptBuilder {
+ // when called first.
+ n.accept = n.accept[0:0]
+ n.charset = n.charset[0:0]
+ n.encoding = n.encoding[0:0]
+
+ // when called after.
+ if len(n.lastAccept) > 0 {
+ n.accept = append(n.accept, n.lastAccept...)
+ n.lastAccept = n.lastAccept[0:0]
+ }
+
+ if len(n.lastCharset) > 0 {
+ n.charset = append(n.charset, n.lastCharset...)
+ n.lastCharset = n.lastCharset[0:0]
+ }
+
+ if len(n.lastEncoding) > 0 {
+ n.encoding = append(n.encoding, n.lastEncoding...)
+ n.lastEncoding = n.lastEncoding[0:0]
+ }
+
+ return n
+}
+
+// MIME adds accepted client's mime type(s).
+// Returns itself.
+func (n *NegotiationAcceptBuilder) MIME(mimeType ...string) *NegotiationAcceptBuilder {
+ n.lastAccept = mimeType
+ n.accept = append(n.accept, mimeType...)
+ return n
+}
+
+// Text adds the "text/plain" as accepted client content type.
+// Returns itself.
+func (n *NegotiationAcceptBuilder) Text() *NegotiationAcceptBuilder {
+ return n.MIME(ContentTextHeaderValue)
+}
+
+// HTML adds the "text/html" as accepted client content type.
+// Returns itself.
+func (n *NegotiationAcceptBuilder) HTML() *NegotiationAcceptBuilder {
+ return n.MIME(ContentHTMLHeaderValue)
+}
+
+// Markdown adds the "text/markdown" as accepted client content type.
+// Returns itself.
+func (n *NegotiationAcceptBuilder) Markdown() *NegotiationAcceptBuilder {
+ return n.MIME(ContentMarkdownHeaderValue)
+}
+
+// Binary adds the "application/octet-stream" as accepted client content type.
+// Returns itself.
+func (n *NegotiationAcceptBuilder) Binary() *NegotiationAcceptBuilder {
+ return n.MIME(ContentBinaryHeaderValue)
+}
+
+// JSON adds the "application/json" as accepted client content type.
+// Returns itself.
+func (n *NegotiationAcceptBuilder) JSON() *NegotiationAcceptBuilder {
+ return n.MIME(ContentJSONHeaderValue)
+}
+
+// JSONP adds the "application/javascript" as accepted client content type.
+// Returns itself.
+func (n *NegotiationAcceptBuilder) JSONP() *NegotiationAcceptBuilder {
+ return n.MIME(ContentJavascriptHeaderValue)
+}
+
+// XML adds the "text/xml" and "application/xml" as accepted client content types.
+// Returns itself.
+func (n *NegotiationAcceptBuilder) XML() *NegotiationAcceptBuilder {
+ return n.MIME(ContentXMLHeaderValue, ContentXMLUnreadableHeaderValue)
+}
+
+// YAML adds the "application/x-yaml" as accepted client content type.
+// Returns itself.
+func (n *NegotiationAcceptBuilder) YAML() *NegotiationAcceptBuilder {
+ return n.MIME(ContentYAMLHeaderValue)
+}
+
+// Charset adds one or more client accepted charsets.
+// Returns itself.
+func (n *NegotiationAcceptBuilder) Charset(charset ...string) *NegotiationAcceptBuilder {
+ n.lastCharset = charset
+ n.charset = append(n.charset, charset...)
+
+ return n
+}
+
+// Encoding adds one or more client accepted encoding algorithms.
+// Returns itself.
+func (n *NegotiationAcceptBuilder) Encoding(encoding ...string) *NegotiationAcceptBuilder {
+ n.lastEncoding = encoding
+ n.encoding = append(n.encoding, encoding...)
+
+ return n
+}
+
+// EncodingGzip adds the "gzip" as accepted encoding.
+// Returns itself.
+func (n *NegotiationAcceptBuilder) EncodingGzip() *NegotiationAcceptBuilder {
+ return n.Encoding(GzipHeaderValue)
+}
+
// +------------------------------------------------------------+
// | Serve files |
// +------------------------------------------------------------+
diff --git a/core/router/api_builder.go b/core/router/api_builder.go
index 35005f8732..b5aee7a729 100644
--- a/core/router/api_builder.go
+++ b/core/router/api_builder.go
@@ -10,6 +10,7 @@ import (
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
"github.com/kataras/iris/macro"
+ macroHandler "github.com/kataras/iris/macro/handler"
)
// MethodNone is a Virtual method
@@ -109,6 +110,20 @@ func (repo *repository) get(routeName string) *Route {
return nil
}
+func (repo *repository) getRelative(r *Route) *Route {
+ if r.tmpl.IsTrailing() || !macroHandler.CanMakeHandler(r.tmpl) {
+ return nil
+ }
+
+ for _, route := range repo.routes {
+ if r.Subdomain == route.Subdomain && r.Method == route.Method && r.FormattedPath == route.FormattedPath && !route.tmpl.IsTrailing() {
+ return route
+ }
+ }
+
+ return nil
+}
+
func (repo *repository) getByPath(tmplPath string) *Route {
if repo.pos != nil {
if idx, ok := repo.pos[tmplPath]; ok {
@@ -345,6 +360,8 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
var route *Route // the last one is returned.
for _, route = range routes {
// global
+
+ route.topLink = api.routes.getRelative(route)
api.routes.register(route)
}
diff --git a/core/router/handler.go b/core/router/handler.go
index 4091e24154..b63d0979b9 100644
--- a/core/router/handler.go
+++ b/core/router/handler.go
@@ -5,11 +5,12 @@ import (
"sort"
"strings"
- "github.com/kataras/golog"
-
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
"github.com/kataras/iris/core/netutil"
+ macroHandler "github.com/kataras/iris/macro/handler"
+
+ "github.com/kataras/golog"
)
// RequestHandler the middle man between acquiring a context and releasing it.
@@ -84,6 +85,13 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
rp := errors.NewReporter()
registeredRoutes := provider.GetRoutes()
+ // before sort.
+ for _, r := range registeredRoutes {
+ if r.topLink != nil {
+ bindMultiParamTypesHandler(r.topLink, r)
+ }
+ }
+
// sort, subdomains go first.
sort.Slice(registeredRoutes, func(i, j int) bool {
first, second := registeredRoutes[i], registeredRoutes[j]
@@ -116,29 +124,57 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
})
for _, r := range registeredRoutes {
- // build the r.Handlers based on begin and done handlers, if any.
- r.BuildHandlers()
-
if r.Subdomain != "" {
h.hosts = true
}
- // the only "bad" with this is if the user made an error
- // on route, it will be stacked shown in this build state
- // and no in the lines of the user's action, they should read
- // the docs better. Or TODO: add a link here in order to help new users.
- if err := h.addRoute(r); err != nil {
- // node errors:
- rp.Add("%v -> %s", err, r.String())
- continue
+ if r.topLink == nil {
+ // build the r.Handlers based on begin and done handlers, if any.
+ r.BuildHandlers()
+
+ // the only "bad" with this is if the user made an error
+ // on route, it will be stacked shown in this build state
+ // and no in the lines of the user's action, they should read
+ // the docs better. Or TODO: add a link here in order to help new users.
+ if err := h.addRoute(r); err != nil {
+ // node errors:
+ rp.Add("%v -> %s", err, r.String())
+ continue
+ }
}
- golog.Debugf(r.Trace())
+ golog.Debugf(r.Trace()) // keep log different parameter types in the same path as different routes.
}
return rp.Return()
}
+func bindMultiParamTypesHandler(top *Route, r *Route) {
+ r.BuildHandlers()
+
+ h := r.Handlers[1:] // remove the macro evaluator handler as we manually check below.
+ f := macroHandler.MakeFilter(r.tmpl)
+ if f == nil {
+ return // should never happen, previous checks made to set the top link.
+ }
+
+ decisionHandler := func(ctx context.Context) {
+ currentRouteName := ctx.RouteName()
+ if f(ctx) {
+ ctx.SetCurrentRouteName(r.Name)
+ ctx.HandlerIndex(0)
+ ctx.Do(h)
+ return
+ }
+
+ ctx.SetCurrentRouteName(currentRouteName)
+ ctx.StatusCode(http.StatusOK)
+ ctx.Next()
+ }
+
+ r.topLink.beginHandlers = append(context.Handlers{decisionHandler}, r.topLink.beginHandlers...)
+}
+
func (h *routerHandler) HandleRequest(ctx context.Context) {
method := ctx.Method()
path := ctx.Path()
diff --git a/core/router/route.go b/core/router/route.go
index a7f53a09e5..9746e4231c 100644
--- a/core/router/route.go
+++ b/core/router/route.go
@@ -40,6 +40,8 @@ type Route struct {
// route, manually or automatic by the framework,
// get the route by `Application#GetRouteByPath(staticSite.RequestPath)`.
StaticSites []context.StaticSite `json:"staticSites"`
+
+ topLink *Route
}
// NewRoute returns a new route based on its method,
diff --git a/doc.go b/doc.go
index fca949ed2e..f9593e8025 100644
--- a/doc.go
+++ b/doc.go
@@ -38,13 +38,13 @@ Source code and other details for the project are available at GitHub:
Current Version
-11.2.2
+11.2.3
Installation
The only requirement is the Go Programming Language, at least version 1.12.
- $ go get github.com/kataras/iris@v11.2.2
+ $ go get github.com/kataras/iris@master
Wiki:
diff --git a/go.mod b/go.mod
index 313be63d3f..e54413743b 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/kataras/iris
go 1.12
require (
+ github.com/kataras/neffos v0.0.9
github.com/BurntSushi/toml v0.3.1
github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible // indirect
github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7 // indirect
@@ -20,7 +21,6 @@ require (
github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0
github.com/json-iterator/go v1.1.6
github.com/kataras/golog v0.0.0-20190624001437-99c81de45f40
- github.com/kataras/neffos v0.0.8
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d // indirect
github.com/mediocregopher/radix/v3 v3.3.0
github.com/microcosm-cc/bluemonday v1.0.2
diff --git a/go.sum b/go.sum
index 829f1b6a13..d2b849840e 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+github.com/kataras/neffos v0.0.9 h1:P9xJ78Q3/BGEfUJe85L1keVm258ZzaiR5tREPZzGzUY=
+github.com/kataras/neffos v0.0.9/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w=
github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
@@ -15,8 +17,6 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/
github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/kataras/golog v0.0.0-20190624001437-99c81de45f40/go.mod h1:PcaEvfvhGsqwXZ6S3CgCbmjcp+4UDUh2MIfF2ZEul8M=
-github.com/kataras/neffos v0.0.8 h1:mOv06sotaVbwCszRaRVerbDRP1embTR7y5J5dhnjjYs=
-github.com/kataras/neffos v0.0.8/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw=
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
diff --git a/go19.go b/go19.go
index 79cb6b7613..d98299c56d 100644
--- a/go19.go
+++ b/go19.go
@@ -90,4 +90,12 @@ type (
//
// An alias for the `context/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()`
+ // server side values. If no matched mime then its "Other" field will be sent,
+ // which should be a string or []byte.
+ // It completes the `context/context.ContentSelector` interface.
+ //
+ // An alias for the `context/Context#N`.
+ N = context.N
)
diff --git a/iris.go b/iris.go
index a5aae22961..8cb0f36c4e 100644
--- a/iris.go
+++ b/iris.go
@@ -37,7 +37,7 @@ import (
var (
// Version is the current version number of the Iris Web Framework.
- Version = "11.2.2"
+ Version = "11.2.3"
)
// HTTP status codes as registered with IANA.
diff --git a/macro/handler/handler.go b/macro/handler/handler.go
index ea36c7d566..1cf7445000 100644
--- a/macro/handler/handler.go
+++ b/macro/handler/handler.go
@@ -34,23 +34,54 @@ func CanMakeHandler(tmpl macro.Template) (needsMacroHandler bool) {
// If the template does not contain any dynamic attributes and a special handler is NOT required
// then it returns a nil handler.
func MakeHandler(tmpl macro.Template) context.Handler {
+ filter := MakeFilter(tmpl)
+
+ return func(ctx context.Context) {
+ if !filter(ctx) {
+ ctx.StopExecution()
+ return
+ }
+
+ // if all passed, just continue.
+ ctx.Next()
+ }
+}
+
+// MakeFilter returns a Filter which reports whether a specific macro template
+// and its parameters pass the serve-time validation.
+func MakeFilter(tmpl macro.Template) context.Filter {
if !CanMakeHandler(tmpl) {
return nil
}
- return func(ctx context.Context) {
+ return func(ctx context.Context) bool {
for _, p := range tmpl.Params {
if !p.CanEval() {
continue // allow.
}
- if !p.Eval(ctx.Params().Get(p.Name), &ctx.Params().Store) {
+ // 07-29-2019
+ // changed to retrieve by param index in order to support
+ // different parameter names for routes with
+ // different param types (and probably different param names i.e {name:string}, {id:uint64})
+ // in the exact same path pattern.
+ //
+ // Same parameter names are not allowed, different param types in the same path
+ // should have different name e.g. {name} {id:uint64};
+ // something like {name} and {name:uint64}
+ // is bad API design and we do NOT allow it by-design.
+ entry, found := ctx.Params().Store.GetEntryAt(p.Index)
+ if !found {
+ // should never happen.
+ return false
+ }
+
+ if !p.Eval(entry.String(), &ctx.Params().Store) {
ctx.StatusCode(p.ErrCode)
- ctx.StopExecution()
- return
+ return false
}
}
- // if all passed, just continue.
- ctx.Next()
+
+ return true
}
}
diff --git a/macro/template.go b/macro/template.go
index 8e766cb5dd..fcdb0b096a 100644
--- a/macro/template.go
+++ b/macro/template.go
@@ -20,6 +20,11 @@ type Template struct {
Params []TemplateParam `json:"params"`
}
+// IsTrailing reports whether this Template is a traling one.
+func (t *Template) IsTrailing() bool {
+ return len(t.Params) > 0 && ast.IsTrailing(t.Params[len(t.Params)-1].Type)
+}
+
// TemplateParam is the parsed macro parameter's template
// they are being used to describe the param's syntax result.
type TemplateParam struct {
diff --git a/websocket/websocket.go b/websocket/websocket.go
index e1389d6ab6..c39e4c7d1a 100644
--- a/websocket/websocket.go
+++ b/websocket/websocket.go
@@ -1,6 +1,8 @@
package websocket
import (
+ "net/http"
+
"github.com/kataras/iris/context"
"github.com/kataras/neffos"
@@ -138,6 +140,14 @@ func SetDefaultUnmarshaler(fn func(data []byte, v interface{}) error) {
// IDGenerator is an iris-specific IDGenerator for new connections.
type IDGenerator func(context.Context) string
+func wrapIDGenerator(idGen IDGenerator) func(ctx context.Context) neffos.IDGenerator {
+ return func(ctx context.Context) neffos.IDGenerator {
+ return func(w http.ResponseWriter, r *http.Request) string {
+ return idGen(ctx)
+ }
+ }
+}
+
// Handler returns an Iris handler to be served in a route of an Iris application.
// Accepts the neffos websocket server as its first input argument
// and optionally an Iris-specific `IDGenerator` as its second one.
@@ -151,19 +161,19 @@ func Handler(s *neffos.Server, IDGenerator ...IDGenerator) context.Handler {
if ctx.IsStopped() {
return
}
- Upgrade(ctx, idGen(ctx), s)
+ Upgrade(ctx, idGen, s)
}
}
// Upgrade upgrades the request and returns a new websocket Conn.
// Use `Handler` for higher-level implementation instead.
-func Upgrade(ctx context.Context, customID string, s *neffos.Server) *neffos.Conn {
+func Upgrade(ctx context.Context, idGen IDGenerator, s *neffos.Server) *neffos.Conn {
conn, _ := s.Upgrade(ctx.ResponseWriter(), ctx.Request(), func(socket neffos.Socket) neffos.Socket {
return &socketWrapper{
Socket: socket,
ctx: ctx,
}
- }, customID)
+ }, wrapIDGenerator(idGen)(ctx))
return conn
}
diff --git a/websocket/websocket_go19.go b/websocket/websocket_go19.go
index e5cccb3beb..fa611adedc 100644
--- a/websocket/websocket_go19.go
+++ b/websocket/websocket_go19.go
@@ -17,6 +17,9 @@ type (
GorillaDialerOptions = gorilla.Options
// GobwasDialerOptions is just an alias for the `gorilla/websocket.Dialer` struct type.
GobwasDialerOptions = gobwas.Options
+ // GobwasHeader is an alias to the adapter that allows the use of `http.Header` as
+ // handshake headers.
+ GobwasHeader = gobwas.Header
// Conn describes the main websocket connection's functionality.
// Its `Connection` will return a new `NSConn` instance.