mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 08:16:28 +01:00
New version 11.2.3 | #1316
Version 11.2.3 Former-commit-id: 461340787defef3171a26ded3e696b0db3bddd95
This commit is contained in:
commit
e57421021c
18
HISTORY.md
18
HISTORY.md
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
## 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://bit.ly/iris-req-book)
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -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
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
36
_examples/http_request/read-yaml/main.go
Normal file
36
_examples/http_request/read-yaml/main.go
Normal 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"))
|
||||||
|
}
|
24
_examples/http_request/read-yaml/main_test.go
Normal file
24
_examples/http_request/read-yaml/main_test.go
Normal 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)
|
||||||
|
}
|
114
_examples/http_responsewriter/content-negotiation/main.go
Normal file
114
_examples/http_responsewriter/content-negotiation/main.go
Normal 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"))
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>.")
|
||||||
}
|
}
|
||||||
|
|
89
_examples/routing/basic/main_test.go
Normal file
89
_examples/routing/basic/main_test.go
Normal 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)
|
||||||
|
|
||||||
|
}
|
|
@ -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"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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 |
|
||||||
// +------------------------------------------------------------+
|
// +------------------------------------------------------------+
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
4
doc.go
|
@ -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
2
go.mod
|
@ -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
4
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/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=
|
||||||
|
|
8
go19.go
8
go19.go
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
2
iris.go
2
iris.go
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user