diff --git a/DONATIONS.md b/DONATIONS.md index ee561d42..0fc2d171 100644 --- a/DONATIONS.md +++ b/DONATIONS.md @@ -10,12 +10,13 @@ Feel free to send **any** amount through paypal [![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=kataras2006%40hotmail%2ecom&lc=GR&item_name=Iris%20web%20framework&item_number=iriswebframeworkdonationid2016¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted&return=http://iris-go.com/assets/v4-book/iris.pdf&cancel_return=https://www.gitbook.com/book/kataras/iris/details) -> Please check your e-mail after your donation, I will ask you if you want to public your donation +> Please check your e-mail after your donation Benefits: -- Your github username is visible to the very-top of the README page +- Your github username,after your approval, is visible here . I respect your privacy at any case. - Access to the 'donors' [private chat room](https://kataras.rocket.chat/group/donors), real-time assistance by me. +- Each donate gives lifetime to the Iris web framework. The author works full-time on this project, no time for any part-time job. **Thank you**! @@ -33,7 +34,7 @@ I'm grateful for all the generous donations. Iris is fully funded by these dona - ANONYMOUS(Waiting For Approval) donated 6 EUR at October 1 - [Ankur Srivastava](https://github.com/ansrivas) donated 20 EUR at October 2 -> The name of the donator added after his/her permission. +> The name or/and github username link added after donator's approvement. #### Report, so far diff --git a/HISTORY.md b/HISTORY.md index e88fa8da..b3a9c426 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,62 @@ **How to upgrade**: remove your `$GOPATH/src/github.com/kataras` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`. +## 4.4.9 -> 4.5.0 + +- **NEW**: Websocket configuration fields: + - `Error func(ctx *Context, status int, reason string)`. Manually catch any handshake errors. Default calls the `ctx.EmitError(status)` with a stored error message in the `WsError` key(`ctx.Set("WsError", reason)`), as before. + - `CheckOrigin func(ctx *Context)`. Manually allow or dissalow client's websocket access, ex: via header **Origin**. Default allow all origins(CORS-like) as before. + - `Headers bool`. Allow websocket handler to copy request's headers on the handshake. Default is true + With these in-mind the `WebsocketConfiguration` seems like this now : + ```go + type WebsocketConfiguration struct { + // WriteTimeout time allowed to write a message to the connection. + // Default value is 15 * time.Second + WriteTimeout time.Duration + // PongTimeout allowed to read the next pong message from the connection + // Default value is 60 * time.Second + PongTimeout time.Duration + // PingPeriod send ping messages to the connection with this period. Must be less than PongTimeout + // Default value is (PongTimeout * 9) / 10 + PingPeriod time.Duration + // MaxMessageSize max message size allowed from connection + // Default value is 1024 + MaxMessageSize int64 + // BinaryMessages set it to true in order to denotes binary data messages instead of utf-8 text + // see https://github.com/kataras/iris/issues/387#issuecomment-243006022 for more + // defaults to false + BinaryMessages bool + // Endpoint is the path which the websocket server will listen for clients/connections + // Default value is empty string, if you don't set it the Websocket server is disabled. + Endpoint string + // ReadBufferSize is the buffer size for the underline reader + ReadBufferSize int + // WriteBufferSize is the buffer size for the underline writer + WriteBufferSize int + // Headers if true then the client's headers are copy to the websocket connection + // + // Default is true + Headers bool + // Error specifies the function for generating HTTP error responses. + // + // The default behavior is to store the reason in the context (ctx.Set(reason)) and fire any custom error (ctx.EmitError(status)) + Error func(ctx *Context, status int, reason string) + // CheckOrigin returns true if the request Origin header is acceptable. If + // CheckOrigin is nil, the host in the Origin header must not be set or + // must match the host of the request. + // + // The default behavior is to allow all origins + // you can change this behavior by setting the iris.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin + CheckOrigin func(ctx *Context) bool + } + ``` + +- **REMOVE**: `github.com/kataras/iris/context/context.go` , this is no needed anymore. Its only usage was inside `sessions` and `websockets`, a month ago I did improvements to the sessions as a standalone package, the IContext interface is not being used there. With the today's changes, the iris-contrib/websocket doesn't needs the IContext interface too, so the whole folder `./context` is useless and removed now. Users developers don't have any side-affects from this change. + + +[Examples](https://github.com/iris-contrib/examples), [Book](https://github.com/iris-contrib/gitbook) are up-to-date, just new configuration fields. + + ## 4.4.8 -> 4.4.9 - **FIX**: Previous CORS fix wasn't enough and produces error before server's startup[*](https://github.com/kataras/iris/issues/461) if many paths were trying to auto-register an `.OPTIONS` route, now this is fixed in combined with some improvements on the [cors middleware](https://github.com/iris-contrib/middleware/tree/master/cors) too. diff --git a/README.md b/README.md index 42652318..dd9934ad 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@
-Releases +Releases Examples @@ -41,14 +41,6 @@ Ideally suited for both experienced and novice Developers. - Check out [this](https://t.co/Y7bK0THScG) Meetup with GoLang User Group. Contains ~30 minutes talk about the Iris web framework. Happens at Thursday, October 13, 2016. -#### Donations -I'm grateful for all the generous donations. Iris is fully funded by these donations[*](DONATIONS.md). - -- [Ryan Brooks](https://github.com/ryanbyyc) donated 50 EUR at May 11 -- [Juan Sebastián Suárez Valencia](https://github.com/Juanses) donated 20 EUR at September 11 -- [Bob Lee](https://github.com/li3p) donated 20 EUR at September 16 -- [Celso Luiz](https://github.com/celsosz) donated 50 EUR at September 29 -- [Ankur Srivastava](https://github.com/ansrivas) donated 20 EUR at October 2 ## Feature Overview @@ -870,7 +862,7 @@ I recommend writing your API tests using this new library, [httpexpect](https:// Versioning ------------ -Current: **v4.4.9** +Current: **v4.5.0** > Iris is an active project @@ -906,7 +898,7 @@ This project is licensed under the [MIT License](LICENSE), Copyright (c) 2016 Ge [Travis]: http://travis-ci.org/kataras/iris [License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square [License]: https://github.com/kataras/iris/blob/master/LICENSE -[Release Widget]: https://img.shields.io/badge/release-4.4.9%20-blue.svg?style=flat-square +[Release Widget]: https://img.shields.io/badge/release-4.5.0%20-blue.svg?style=flat-square [Release]: https://github.com/kataras/iris/releases [Chat Widget]: https://img.shields.io/badge/community-chat%20-00BCD4.svg?style=flat-square [Chat]: https://kataras.rocket.chat/channel/iris diff --git a/configuration.go b/configuration.go index a0f14e72..c644f445 100644 --- a/configuration.go +++ b/configuration.go @@ -6,6 +6,7 @@ import ( "github.com/kataras/go-sessions" "github.com/valyala/fasthttp" "io" + "net/url" "os" "strconv" "time" @@ -659,6 +660,21 @@ type WebsocketConfiguration struct { ReadBufferSize int // WriteBufferSize is the buffer size for the underline writer WriteBufferSize int + // Headers if true then the client's headers are copy to the websocket connection + // + // Default is true + Headers bool + // Error specifies the function for generating HTTP error responses. + // + // The default behavior is to store the reason in the context (ctx.Set(reason)) and fire any custom error (ctx.EmitError(status)) + Error func(ctx *Context, status int, reason string) + // CheckOrigin returns true if the request Origin header is acceptable. If + // CheckOrigin is nil, the host in the Origin header must not be set or + // must match the host of the request. + // + // The default behavior is to allow all origins + // you can change this behavior by setting the iris.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin + CheckOrigin func(ctx *Context) bool } var ( @@ -717,6 +733,26 @@ var ( c.Websocket.WriteBufferSize = val } } + // OptionWebsocketHeaders if true then the client's headers are copy to the websocket connection + OptionWebsocketHeaders = func(val bool) OptionSet { + return func(c *Configuration) { + c.Websocket.Headers = val + } + } + // OptionWebsocketError specifies the function for generating HTTP error responses. + OptionWebsocketError = func(val func(*Context, int, string)) OptionSet { + return func(c *Configuration) { + c.Websocket.Error = val + } + } + // OptionWebsocketCheckOrigin returns true if the request Origin header is acceptable. If + // CheckOrigin is nil, the host in the Origin header must not be set or + // must match the host of the request. + OptionWebsocketCheckOrigin = func(val func(*Context) bool) OptionSet { + return func(c *Configuration) { + c.Websocket.CheckOrigin = val + } + } ) const ( @@ -730,6 +766,31 @@ const ( DefaultMaxMessageSize = 1024 ) +var ( + // DefaultWebsocketError is the default method to manage the handshake websocket errors + DefaultWebsocketError = func(ctx *Context, status int, reason string) { + ctx.Set("WsError", reason) + ctx.EmitError(status) + } + // DefaultWebsocketCheckOrigin is the default method to allow websocket clients to connect to this server + // you can change this behavior by setting the iris.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin + DefaultWebsocketCheckOrigin = func(ctx *Context) bool { + return true + } + // WebsocketCheckSameOrigin returns true if the origin is not set or is equal to the request host + WebsocketCheckSameOrigin = func(ctx *Context) bool { + origin := ctx.RequestHeader("origin") + if len(origin) == 0 { + return true + } + u, err := url.Parse(origin) + if err != nil { + return false + } + return u.Host == ctx.HostString() + } +) + // DefaultWebsocketConfiguration returns the default config for iris-ws websocket package func DefaultWebsocketConfiguration() WebsocketConfiguration { return WebsocketConfiguration{ @@ -741,6 +802,7 @@ func DefaultWebsocketConfiguration() WebsocketConfiguration { ReadBufferSize: 4096, WriteBufferSize: 4096, Endpoint: "", + Headers: true, } } diff --git a/context.go b/context.go index e56ad261..f7f3c76b 100644 --- a/context.go +++ b/context.go @@ -1,7 +1,3 @@ -/* -Context.go Implements: ./context/context.go -*/ - package iris import ( @@ -24,7 +20,6 @@ import ( "github.com/kataras/go-errors" "github.com/kataras/go-fs" "github.com/kataras/go-sessions" - "github.com/kataras/iris/context" "github.com/kataras/iris/utils" "github.com/valyala/fasthttp" ) @@ -109,8 +104,6 @@ type ( } ) -var _ context.IContext = &Context{} - // GetRequestCtx returns the current fasthttp context func (ctx *Context) GetRequestCtx() *fasthttp.RequestCtx { return ctx.RequestCtx diff --git a/context/context.go b/context/context.go deleted file mode 100644 index 08adb093..00000000 --- a/context/context.go +++ /dev/null @@ -1,88 +0,0 @@ -package context - -import ( - "bufio" - "github.com/kataras/go-sessions" - "github.com/valyala/fasthttp" - "io" - "time" -) - -type ( - - // IContext the interface for the iris/context - // Used mostly inside packages which shouldn't be import ,directly, the kataras/iris. - IContext interface { - Param(string) string - ParamInt(string) (int, error) - ParamInt64(string) (int64, error) - URLParam(string) string - URLParamInt(string) (int, error) - URLParamInt64(string) (int64, error) - URLParams() map[string]string - MethodString() string - HostString() string - Subdomain() string - PathString() string - RequestPath(bool) string - RequestIP() string - RemoteAddr() string - RequestHeader(k string) string - IsAjax() bool - FormValueString(string) string - FormValues(string) []string - PostValuesAll() map[string][]string - PostValues(name string) []string - PostValue(name string) string - SetStatusCode(int) - SetContentType(string) - SetHeader(string, string) - Redirect(string, ...int) - RedirectTo(string, ...interface{}) - NotFound() - Panic() - EmitError(int) - Write(string, ...interface{}) - HTML(int, string) - Data(int, []byte) error - RenderWithStatus(int, string, interface{}, ...map[string]interface{}) error - Render(string, interface{}, ...map[string]interface{}) error - MustRender(string, interface{}, ...map[string]interface{}) - TemplateString(string, interface{}, ...map[string]interface{}) string - MarkdownString(string) string - Markdown(int, string) - JSON(int, interface{}) error - JSONP(int, string, interface{}) error - Text(int, string) error - XML(int, interface{}) error - ServeContent(io.ReadSeeker, string, time.Time, bool) error - ServeFile(string, bool) error - SendFile(string, string) - Stream(func(*bufio.Writer)) - StreamWriter(cb func(*bufio.Writer)) - StreamReader(io.Reader, int) - ReadJSON(interface{}) error - ReadXML(interface{}) error - ReadForm(interface{}) error - Get(string) interface{} - GetString(string) string - GetInt(string) int - Set(string, interface{}) - VisitAllCookies(func(string, string)) - SetCookie(*fasthttp.Cookie) - SetCookieKV(string, string) - RemoveCookie(string) - GetFlashes() map[string]string - GetFlash(string) (string, error) - SetFlash(string, string) - Session() sessions.Session - SessionDestroy() - Log(string, ...interface{}) - GetRequestCtx() *fasthttp.RequestCtx - Do() - Next() - StopExecution() - IsStopped() bool - GetHandlerName() string - } -) diff --git a/iris.go b/iris.go index 3855c005..3d5685f3 100644 --- a/iris.go +++ b/iris.go @@ -79,7 +79,7 @@ import ( const ( // Version is the current version of the Iris web framework - Version = "4.4.9" + Version = "4.5.0" banner = ` _____ _ |_ _| (_) diff --git a/ssh.go b/ssh.go index 51d263bf..e303e03d 100644 --- a/ssh.go +++ b/ssh.go @@ -27,6 +27,8 @@ package iris // log // help // exit +// +// Keep note that I will re-write this file, ssh.go because, as you can see, it's not well-written and not maintainable* import ( "bytes" diff --git a/websocket.go b/websocket.go index 3f8204e3..9b7d62fa 100644 --- a/websocket.go +++ b/websocket.go @@ -23,6 +23,16 @@ type ( WebsocketServer struct { websocket.Server upgrader irisWebsocket.Upgrader + + // the only fields we need at runtime here for iris-specific error and check origin funcs + // they comes from WebsocketConfiguration + + // Error specifies the function for generating HTTP error responses. + Error func(ctx *Context, status int, reason string) + // CheckOrigin returns true if the request Origin header is acceptable. If + // CheckOrigin is nil, the host in the Origin header must not be set or + // must match the host of the request. + CheckOrigin func(ctx *Context) bool } ) @@ -40,17 +50,32 @@ func NewWebsocketServer() *WebsocketServer { // If the upgrade fails, then Upgrade replies to the client with an HTTP error // response. func (s *WebsocketServer) Upgrade(ctx *Context) error { - return s.upgrader.Upgrade(ctx) + return s.upgrader.Upgrade(ctx.RequestCtx) } // Handler is the iris Handler to upgrade the request // used inside RegisterRoutes func (s *WebsocketServer) Handler(ctx *Context) { + // first, check origin + if !s.CheckOrigin(ctx) { + s.Error(ctx, StatusForbidden, "websocket: origin not allowed") + return + } + + // all other errors comes from the underline iris-contrib/websocket if err := s.Upgrade(ctx); err != nil { if ctx.framework.Config.IsDevelopment { ctx.Log("Websocket error while trying to Upgrade the connection. Trace: %s", err.Error()) } - ctx.EmitError(StatusBadRequest) + + statusErrCode := StatusBadRequest + if herr, isHandshake := err.(irisWebsocket.HandshakeError); isHandshake { + statusErrCode = herr.Status() + } + // if not handshake error just fire the custom(if any) StatusBadRequest + // with the websocket's error message in the ctx.Get("WsError") + DefaultWebsocketError(ctx, statusErrCode, err.Error()) + } } @@ -64,7 +89,7 @@ func (s *WebsocketServer) RegisterTo(station *Framework, c WebsocketConfiguratio s.Server = websocket.New() } // is just a conversional type for kataras/go-websocket.Connection - s.upgrader = irisWebsocket.Custom(s.Server.HandleConnection, c.ReadBufferSize, c.WriteBufferSize, false) + s.upgrader = irisWebsocket.Custom(s.Server.HandleConnection, c.ReadBufferSize, c.WriteBufferSize, c.Headers) // set the routing for client-side source (javascript) (optional) clientSideLookupName := "iris-websocket-client-side" @@ -85,6 +110,17 @@ func (s *WebsocketServer) RegisterTo(station *Framework, c WebsocketConfiguratio WriteBufferSize: c.WriteBufferSize, }) + s.Error = c.Error + s.CheckOrigin = c.CheckOrigin + + if s.Error == nil { + s.Error = DefaultWebsocketError + } + + if s.CheckOrigin == nil { + s.CheckOrigin = DefaultWebsocketCheckOrigin + } + // run the ws server s.Server.Serve()