New version 11.2.3 | #1316

Version 11.2.3

Former-commit-id: 461340787defef3171a26ded3e696b0db3bddd95
This commit is contained in:
Gerasimos (Makis) Maropoulos 2019-08-03 08:55:32 +03:00 committed by GitHub
commit e57421021c
32 changed files with 1275 additions and 194 deletions

View File

@ -19,15 +19,16 @@
Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready. 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 - Different parameter types in the same path (done).
- https://github.com/kataras/iris/issues/1207 - [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: 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. - 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. - 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 # 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

View File

@ -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. 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 ## 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? ### Do you like to read while traveling?
<a href="https://bit.ly/iris-req-book"> <img alt="Book cover" src="https://iris-go.com/images/iris-book-cover.jpg" width="200" /> </a>
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. 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 ## Contributing

View File

@ -8,7 +8,7 @@ Iris 是基于 Go 编写的一个快速,简单但功能齐全且非常高效
看看 [其他人如何评价 Iris](https://iris-go.com/testimonials/),同时欢迎各位点亮 **star** 看看 [其他人如何评价 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 ## 学习 Iris

View File

@ -1 +1 @@
11.2.1:https://dev.to/kataras/iris-version-11-2-released-22bc 11.2.3:https://github.com/kataras/iris/releases/tag/v11.2.3

View File

@ -224,6 +224,7 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her
- [Read JSON](http_request/read-json/main.go) - [Read JSON](http_request/read-json/main.go)
* [Struct Validation](http_request/read-json-struct-validation/main.go) * [Struct Validation](http_request/read-json-struct-validation/main.go)
- [Read XML](http_request/read-xml/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 Form](http_request/read-form/main.go)
- [Read Query](http_request/read-query/main.go) **NEW** - [Read Query](http_request/read-query/main.go) **NEW**
- [Read Custom per type](http_request/read-custom-per-type/main.go) - [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` ### How to Write to `context.ResponseWriter() http.ResponseWriter`
- [Content Negotiation](http_responsewriter/content-negotiation) **NEW**
- [Write `valyala/quicktemplate` templates](http_responsewriter/quicktemplate) - [Write `valyala/quicktemplate` templates](http_responsewriter/quicktemplate)
- [Write `shiyanhui/hero` templates](http_responsewriter/herotemplate) - [Write `shiyanhui/hero` templates](http_responsewriter/herotemplate)
- [Text, Markdown, HTML, JSON, JSONP, XML, Binary](http_responsewriter/write-rest/main.go) - [Text, Markdown, HTML, JSON, JSONP, XML, Binary](http_responsewriter/write-rest/main.go)

View File

@ -330,6 +330,7 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her
- [读取JSON](http_request/read-json/main.go) - [读取JSON](http_request/read-json/main.go)
- [读取XML](http_request/read-xml/main.go) - [读取XML](http_request/read-xml/main.go)
- [读取YAML](http_request/read-yaml/main.go) **更新**
- [读取Form](http_request/read-form/main.go) - [读取Form](http_request/read-form/main.go)
- [读取Query](http_request/read-query/main.go) **更新** - [读取Query](http_request/read-query/main.go) **更新**
- [读取每个类型的自定义结果Custom per type](http_request/read-custom-per-type/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` ### 如何写入`context.ResponseWriter() http.ResponseWriter`
- [Content Negotiation](http_responsewriter/content-negotiation) **更新**
- [`valyala/quicktemplate`模版](http_responsewriter/quicktemplate) - [`valyala/quicktemplate`模版](http_responsewriter/quicktemplate)
- [`shiyanhui/hero`模版](http_responsewriter/herotemplate) - [`shiyanhui/hero`模版](http_responsewriter/herotemplate)
- [Text, Markdown, HTML, JSON, JSONP, XML, Binary](http_responsewriter/write-rest/main.go) - [Text, Markdown, HTML, JSON, JSONP, XML, Binary](http_responsewriter/write-rest/main.go)

View File

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

View File

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

View File

@ -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("<h1>Test Name</h1><h2>Age 26</h2>")
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: "<h1>Test Name</h1><h2>Age 26</h2>", // for text/html
})
})
return app
}
func main() {
app := newApp()
app.Run(iris.Addr(":8080"))
}

View File

@ -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 = "<h1>Test Name</h1><h2>Age 26</h2>"
)
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)
}
}

View File

@ -13,7 +13,9 @@ func main() {
ctx.HTML("<h1> Please click <a href='/debug/pprof'>here</a>") ctx.HTML("<h1> Please click <a href='/debug/pprof'>here</a>")
}) })
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")) app.Run(iris.Addr(":8080"))
} }

View File

@ -4,8 +4,9 @@ import (
"github.com/kataras/iris" "github.com/kataras/iris"
) )
func main() { func newApp() *iris.Application {
app := iris.New() app := iris.New()
app.Logger().SetLevel("debug")
// registers a custom handler for 404 not found http (error) status code, // registers a custom handler for 404 not found http (error) status code,
// fires when route not found or manually by ctx.StatusCode(iris.StatusNotFound). // 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. // Third receiver should contains the route's handler(s), they are executed by order.
app.Handle("GET", "/", func(ctx iris.Context) { app.Handle("GET", "/", func(ctx iris.Context) {
// navigate to the middle of $GOPATH/src/github.com/kataras/iris/context/context.go // navigate to the https://github.com/kataras/iris/wiki/Routing-context-methods
// to overview all context's method (there a lot of them, read that and you will learn how iris works too) // to overview all context's method.
ctx.HTML("Hello from " + ctx.Path()) // Hello from / ctx.HTML("Hello from " + ctx.Path()) // Hello from /
}) })
@ -26,7 +27,46 @@ func main() {
ctx.Writef(`Same as app.Handle("GET", "/", [...])`) 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"! // Pssst, don't forget dynamic-path example for more "magic"!
app.Get("/api/users/{userid:uint64 min(1)}", func(ctx iris.Context) { 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. { // braces are optional, it's just type of style, to group the routes visually.
// http://v1.localhost:8080 // http://v1.localhost:8080
// Note: for versioning-specific features checkout the _examples/versioning instead.
v1.Get("/", func(ctx iris.Context) { v1.Get("/", func(ctx iris.Context) {
ctx.HTML("Version 1 API. go to <a href='" + ctx.Path() + "/api" + "'>/api/users</a>") ctx.HTML(`Version 1 API. go to <a href="/api/users">/api/users</a>`)
}) })
usersAPI := v1.Party("/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
// http://localhost:8080/home // http://localhost:8080/home
// http://localhost:8080/donate
// http://localhost:8080/api/users/42 // http://localhost:8080/api/users/42
// http://localhost:8080/admin // http://localhost:8080/admin
// http://localhost:8080/admin/login // http://localhost:8080/admin/login
@ -128,6 +174,12 @@ func main() {
// http://localhost:8080/api/users/blabla // http://localhost:8080/api/users/blabla
// http://localhost:8080/wontfound // 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: // if hosts edited:
// http://v1.localhost:8080 // http://v1.localhost:8080
// http://v1.localhost:8080/api/users // 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. 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) { func notFoundHandler(ctx iris.Context) {
ctx.HTML("Custom route for 404 not found http code, here you can render a view, html, json <b>any valid response</b>.") ctx.HTML("Custom route for 404 not found http code, here you can render a view, html, json <b>any valid response</b>.")
} }

View File

@ -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 <b>any valid response</b>."
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 = "<h1>Hello from admin/</h1>"
expectedSubdomainV1IndexResponse = `Version 1 API. go to <a href="/api/users">/api/users</a>`
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)
}

View File

@ -284,5 +284,10 @@ func main() {
// Last, do not confuse `ctx.Params()` with `ctx.Values()`. // Last, do not confuse `ctx.Params()` with `ctx.Values()`.
// Path parameter's values can be retrieved from `ctx.Params()`, // 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()`. // 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")) app.Run(iris.Addr(":8080"))
} }

View File

@ -1,8 +1,6 @@
package main package main
import ( import (
"io/ioutil"
"github.com/kataras/iris" "github.com/kataras/iris"
) )
@ -108,7 +106,7 @@ func newApp() *iris.Application {
// to protect ourselves from "over heating". // to protect ourselves from "over heating".
app.Post("/", iris.LimitRequestBodySize(maxBodySize), func(ctx iris.Context) { app.Post("/", iris.LimitRequestBodySize(maxBodySize), func(ctx iris.Context) {
// get request body // get request body
b, err := ioutil.ReadAll(ctx.Request().Body) b, err := ctx.GetBody()
// if is larger then send a bad request status // if is larger then send a bad request status
if err != nil { if err != nil {
ctx.StatusCode(iris.StatusBadRequest) ctx.StatusCode(iris.StatusBadRequest)

View File

@ -21,6 +21,7 @@ func main() {
Password: "", Password: "",
Database: "", Database: "",
Prefix: "", Prefix: "",
Delim: "-",
}) // optionally configure the bridge between your redis server. }) // optionally configure the bridge between your redis server.
// close connection when control+C/cmd+C // close connection when control+C/cmd+C

View File

@ -53,6 +53,8 @@
}; };
} }
const username = window.prompt("Your username?");
async function runExample() { async function runExample() {
// You can omit the "default" and simply define only Events, the namespace will be an empty string"", // 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. // 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); addMessage(msg.Body);
} }
} }
},{
headers: {
"X-Username": username,
}
}); });
// You can either wait to conenct or just conn.connect("connect") // You can either wait to conenct or just conn.connect("connect")

View File

@ -42,7 +42,10 @@ func main() {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(dialAndConnectTimeout)) ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(dialAndConnectTimeout))
defer cancel() 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 { if err != nil {
panic(err) panic(err)
} }

View File

@ -68,8 +68,17 @@ func main() {
SigningMethod: jwt.SigningMethodHS256, 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 // 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 { if enableJWT {
// Register the jwt middleware (on handshake): // Register the jwt middleware (on handshake):

View File

@ -586,6 +586,10 @@ type Context interface {
// //
// Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go
ReadXML(xmlObjectPtr interface{}) error 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 // ReadForm binds the formObject with the form data
// it supports any kind of type, including custom structs. // it supports any kind of type, including custom structs.
// It will return nothing if request data are empty. // It will return nothing if request data are empty.
@ -781,6 +785,49 @@ type Context interface {
Markdown(markdownB []byte, options ...Markdown) (int, error) Markdown(markdownB []byte, options ...Markdown) (int, error)
// YAML parses the "v" using the yaml parser and renders its result to the client. // YAML parses the "v" using the yaml parser and renders its result to the client.
YAML(v interface{}) (int, error) 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 | // | Serve files |
// +------------------------------------------------------------+ // +------------------------------------------------------------+
@ -1750,7 +1797,9 @@ func (ctx *context) contentTypeOnce(cType string, charset string) {
charset = ctx.Application().ConfigurationReadOnly().GetCharset() charset = ctx.Application().ConfigurationReadOnly().GetCharset()
} }
cType += "; charset=" + charset if cType != ContentBinaryHeaderValue {
cType += "; charset=" + charset
}
ctx.Values().Set(contentTypeContextKey, cType) ctx.Values().Set(contentTypeContextKey, cType)
ctx.writer.Header().Set(ContentTypeHeaderKey, 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. // 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 // 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 var unmarshaler = json.Unmarshal
if ctx.shouldOptimize() { if ctx.shouldOptimize() {
unmarshaler = jsoniter.Unmarshal 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. // 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 // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go
func (ctx *context) ReadXML(xmlObject interface{}) error { func (ctx *context) ReadXML(outPtr interface{}) error {
return ctx.UnmarshalBody(xmlObject, UnmarshalerFunc(xml.Unmarshal)) 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`. // IsErrPath can be used at `context#ReadForm` and `context#ReadQuery`.
@ -2893,126 +2949,6 @@ const (
ContentFormMultipartHeaderValue = "multipart/form-data" 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. // Binary writes out the raw bytes as binary data.
func (ctx *context) Binary(data []byte) (int, error) { func (ctx *context) Binary(data []byte) (int, error) {
ctx.ContentType(ContentBinaryHeaderValue) ctx.ContentType(ContentBinaryHeaderValue)
@ -3319,6 +3255,627 @@ func (ctx *context) YAML(v interface{}) (int, error) {
return ctx.Write(out) 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 | // | Serve files |
// +------------------------------------------------------------+ // +------------------------------------------------------------+

View File

@ -10,6 +10,7 @@ import (
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/errors"
"github.com/kataras/iris/macro" "github.com/kataras/iris/macro"
macroHandler "github.com/kataras/iris/macro/handler"
) )
// MethodNone is a Virtual method // MethodNone is a Virtual method
@ -109,6 +110,20 @@ func (repo *repository) get(routeName string) *Route {
return nil 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 { func (repo *repository) getByPath(tmplPath string) *Route {
if repo.pos != nil { if repo.pos != nil {
if idx, ok := repo.pos[tmplPath]; ok { 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. var route *Route // the last one is returned.
for _, route = range routes { for _, route = range routes {
// global // global
route.topLink = api.routes.getRelative(route)
api.routes.register(route) api.routes.register(route)
} }

View File

@ -5,11 +5,12 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/kataras/golog"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/errors"
"github.com/kataras/iris/core/netutil" "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. // 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() rp := errors.NewReporter()
registeredRoutes := provider.GetRoutes() registeredRoutes := provider.GetRoutes()
// before sort.
for _, r := range registeredRoutes {
if r.topLink != nil {
bindMultiParamTypesHandler(r.topLink, r)
}
}
// sort, subdomains go first. // sort, subdomains go first.
sort.Slice(registeredRoutes, func(i, j int) bool { sort.Slice(registeredRoutes, func(i, j int) bool {
first, second := registeredRoutes[i], registeredRoutes[j] first, second := registeredRoutes[i], registeredRoutes[j]
@ -116,29 +124,57 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
}) })
for _, r := range registeredRoutes { for _, r := range registeredRoutes {
// build the r.Handlers based on begin and done handlers, if any.
r.BuildHandlers()
if r.Subdomain != "" { if r.Subdomain != "" {
h.hosts = true h.hosts = true
} }
// the only "bad" with this is if the user made an error if r.topLink == nil {
// on route, it will be stacked shown in this build state // build the r.Handlers based on begin and done handlers, if any.
// and no in the lines of the user's action, they should read r.BuildHandlers()
// the docs better. Or TODO: add a link here in order to help new users.
if err := h.addRoute(r); err != nil { // the only "bad" with this is if the user made an error
// node errors: // on route, it will be stacked shown in this build state
rp.Add("%v -> %s", err, r.String()) // and no in the lines of the user's action, they should read
continue // 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() 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) { func (h *routerHandler) HandleRequest(ctx context.Context) {
method := ctx.Method() method := ctx.Method()
path := ctx.Path() path := ctx.Path()

View File

@ -40,6 +40,8 @@ type Route struct {
// route, manually or automatic by the framework, // route, manually or automatic by the framework,
// get the route by `Application#GetRouteByPath(staticSite.RequestPath)`. // get the route by `Application#GetRouteByPath(staticSite.RequestPath)`.
StaticSites []context.StaticSite `json:"staticSites"` StaticSites []context.StaticSite `json:"staticSites"`
topLink *Route
} }
// NewRoute returns a new route based on its method, // NewRoute returns a new route based on its method,

4
doc.go
View File

@ -38,13 +38,13 @@ Source code and other details for the project are available at GitHub:
Current Version Current Version
11.2.2 11.2.3
Installation Installation
The only requirement is the Go Programming Language, at least version 1.12. 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: Wiki:

2
go.mod
View File

@ -3,6 +3,7 @@ module github.com/kataras/iris
go 1.12 go 1.12
require ( require (
github.com/kataras/neffos v0.0.9
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v0.3.1
github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible // indirect github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible // indirect
github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7 // 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/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0
github.com/json-iterator/go v1.1.6 github.com/json-iterator/go v1.1.6
github.com/kataras/golog v0.0.0-20190624001437-99c81de45f40 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/kataras/pio v0.0.0-20190103105442-ea782b38602d // indirect
github.com/mediocregopher/radix/v3 v3.3.0 github.com/mediocregopher/radix/v3 v3.3.0
github.com/microcosm-cc/bluemonday v1.0.2 github.com/microcosm-cc/bluemonday v1.0.2

4
go.sum
View File

@ -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/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/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= 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/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/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/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/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/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= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=

View File

@ -90,4 +90,12 @@ type (
// //
// An alias for the `context/Context#CookieOption`. // An alias for the `context/Context#CookieOption`.
CookieOption = 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
) )

View File

@ -37,7 +37,7 @@ import (
var ( var (
// Version is the current version number of the Iris Web Framework. // 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. // HTTP status codes as registered with IANA.

View File

@ -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 // If the template does not contain any dynamic attributes and a special handler is NOT required
// then it returns a nil handler. // then it returns a nil handler.
func MakeHandler(tmpl macro.Template) context.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) { if !CanMakeHandler(tmpl) {
return nil return nil
} }
return func(ctx context.Context) { return func(ctx context.Context) bool {
for _, p := range tmpl.Params { for _, p := range tmpl.Params {
if !p.CanEval() { if !p.CanEval() {
continue // allow. 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.StatusCode(p.ErrCode)
ctx.StopExecution() return false
return
} }
} }
// if all passed, just continue.
ctx.Next() return true
} }
} }

View File

@ -20,6 +20,11 @@ type Template struct {
Params []TemplateParam `json:"params"` 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 // TemplateParam is the parsed macro parameter's template
// they are being used to describe the param's syntax result. // they are being used to describe the param's syntax result.
type TemplateParam struct { type TemplateParam struct {

View File

@ -1,6 +1,8 @@
package websocket package websocket
import ( import (
"net/http"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/neffos" "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. // IDGenerator is an iris-specific IDGenerator for new connections.
type IDGenerator func(context.Context) string type IDGenerator func(context.Context) string
func wrapIDGenerator(idGen IDGenerator) func(ctx context.Context) neffos.IDGenerator {
return func(ctx context.Context) neffos.IDGenerator {
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. // 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 // Accepts the neffos websocket server as its first input argument
// and optionally an Iris-specific `IDGenerator` as its second one. // 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() { if ctx.IsStopped() {
return return
} }
Upgrade(ctx, idGen(ctx), s) Upgrade(ctx, idGen, s)
} }
} }
// Upgrade upgrades the request and returns a new websocket Conn. // Upgrade upgrades the request and returns a new websocket Conn.
// Use `Handler` for higher-level implementation instead. // 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 { conn, _ := s.Upgrade(ctx.ResponseWriter(), ctx.Request(), func(socket neffos.Socket) neffos.Socket {
return &socketWrapper{ return &socketWrapper{
Socket: socket, Socket: socket,
ctx: ctx, ctx: ctx,
} }
}, customID) }, wrapIDGenerator(idGen)(ctx))
return conn return conn
} }

View File

@ -17,6 +17,9 @@ type (
GorillaDialerOptions = gorilla.Options GorillaDialerOptions = gorilla.Options
// GobwasDialerOptions is just an alias for the `gorilla/websocket.Dialer` struct type. // GobwasDialerOptions is just an alias for the `gorilla/websocket.Dialer` struct type.
GobwasDialerOptions = gobwas.Options 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. // Conn describes the main websocket connection's functionality.
// Its `Connection` will return a new `NSConn` instance. // Its `Connection` will return a new `NSConn` instance.