Update to 4.2.0 - Configuration changes (big but old way still works, naming changes) & Implement https://github.com/kataras/iris/issues/409

This commit is contained in:
Gerasimos Maropoulos 2016-09-09 08:09:03 +03:00
parent 1a7c79db66
commit f561b7a90d
18 changed files with 1528 additions and 753 deletions

View File

@ -1,6 +1,286 @@
# History
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras/iris` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras/iris` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris`.
## 4.1.7 -> 4.2.0
- **ADDED**: `iris.TemplateSourceString(src string, binding interface{}) string` this will parse the src raw contents to the template engine and return the string result & `context.RenderTemplateSource(status int, src string, binding interface{}, options ...map[string]interface{}) error` this will parse the src raw contents to the template engine and render the result to the client, as requseted [here](https://github.com/kataras/iris/issues/409).
This version has 'breaking' changes if you were, directly, passing custom configuration to a custom iris instance before.
As the TODO2 I had to think and implement a way to make configuration even easier and more simple to use.
With last changes in place, Iris is using new, cross-framework, and more stable packages made by me(so don't worry things are working and will as you expect) to render [templates](https://github.com/kataras/go-template), manage [sessions](https://github.com/kataras/go-sesions) and [websockets](https://github.com/kataras/go-websocket). So the `/kataras/iris/config` is no longer need to be there, we don't have core packages inside iris which need these configuration to other package-folder than the main anymore(in order to avoid the import-cycle), new file `/kataras/iris/configuration.go` is created for the configuration, which lives inside the main package, means that now:
- **if you want to pass directly configuration to a new custom iris instance, you don't have to import the github.com/kataras/iris/config package**
Naming changes:
- `config.Iris` -> `iris.Configuration`, which is the parent/main configuration. Added: `TimeFormat` and `Other` (pass any dynamic custom, other options there)
- `config.Sessions` -> `iris.SessionsConfiguration`
- `config.Websocket` -> `iris.WebscoketConfiguration`
- `config.Server` -> `iris.ServerConfiguration`
- `config.Tester` -> `iris.TesterConfiguration`
All these changes wasn't made only to remove the `./config` folder but to make easier for you to pass the exact configuration field/option you need to edit at the top of the default configuration, without need to pass the whole Configuration object. **Attention**: old way, pass `iris.Configuration` directly, is still valid object to pass to the `iris.New`, so don't be afraid for breaking change, the only thing you will need to edit is the names of the configuration you saw on the previous paragraph.
**Configuration Declaration**:
instead of old, but still valid to pass to the `iris.New`:
- `iris.New(iris.Configuration{Charset: "UTF-8", Sessions: iris.SessionsConfiguration{Cookie: "cookienameid"}})`
now you can just write this:
- `iris.New(iris.OptionCharset("UTF-8"), iris.OptionSessionsCookie("cookienameid"))`
`.New` **by configuration**
```go
import "github.com/kataras/iris"
//...
myConfig := iris.Configuration{Charset: "UTF-8", IsDevelopment:true, Sessions: iris.SessionsConfiguration{Cookie:"mycookie"}, Websocket: iris.WebsocketConfiguration{Endpoint: "/my_endpoint"}}
iris.New(myConfig)
```
`.New` **by options**
```go
import "github.com/kataras/iris"
//...
iris.New(iris.OptionCharset("UTF-8"), iris.OptionIsDevelopment(true), iris.OptionSessionsCookie("mycookie"), iris.OptionWebsocketEndpoint("/my_endpoint"))
// if you want to set configuration after the .New use the .Set:
iris.Set(iris.OptionDisableBanner(true))
```
**List** of all available options:
```go
// OptionDisablePathCorrection corrects and redirects the requested path to the registed path
// for example, if /home/ path is requested but no handler for this Route found,
// then the Router checks if /home handler exists, if yes,
// (permant)redirects the client to the correct path /home
//
// Default is false
OptionDisablePathCorrection(val bool)
// OptionDisablePathEscape when is false then its escapes the path, the named parameters (if any).
OptionDisablePathEscape(val bool)
// OptionDisableBanner outputs the iris banner at startup
//
// Default is false
OptionDisableBanner(val bool)
// OptionLoggerOut is the destination for output
//
// Default is os.Stdout
OptionLoggerOut(val io.Writer)
// OptionLoggerPreffix is the logger's prefix to write at beginning of each line
//
// Default is [IRIS]
OptionLoggerPreffix(val string)
// OptionProfilePath a the route path, set it to enable http pprof tool
// Default is empty, if you set it to a $path, these routes will handled:
OptionProfilePath(val string)
// OptionDisableTemplateEngines set to true to disable loading the default template engine (html/template) and disallow the use of iris.UseEngine
// Default is false
OptionDisableTemplateEngines(val bool)
// OptionIsDevelopment iris will act like a developer, for example
// If true then re-builds the templates on each request
// Default is false
OptionIsDevelopment(val bool)
// OptionTimeFormat time format for any kind of datetime parsing
OptionTimeFormat(val string)
// OptionCharset character encoding for various rendering
// used for templates and the rest of the responses
// Default is "UTF-8"
OptionCharset(val string)
// OptionGzip enables gzip compression on your Render actions, this includes any type of render, templates and pure/raw content
// If you don't want to enable it globaly, you could just use the third parameter on context.Render("myfileOrResponse", structBinding{}, iris.RenderOptions{"gzip": true})
// Default is false
OptionGzip(val bool)
// OptionOther are the custom, dynamic options, can be empty
// this fill used only by you to set any app's options you want
// for each of an Iris instance
OptionOther(val ...options.Options) //map[string]interface{}, options is github.com/kataras/go-options
// OptionSessionsCookie string, the session's client cookie name, for example: "qsessionid"
OptionSessionsCookie(val string)
// OptionSessionsDecodeCookie set it to true to decode the cookie key with base64 URLEncoding
// Defaults to false
OptionSessionsDecodeCookie(val bool)
// OptionSessionsExpires the duration of which the cookie must expires (created_time.Add(Expires)).
// If you want to delete the cookie when the browser closes, set it to -1 but in this case, the server side's session duration is up to GcDuration
//
// Default infinitive/unlimited life duration(0)
OptionSessionsExpires(val time.Duration)
// OptionSessionsCookieLength the length of the sessionid's cookie's value, let it to 0 if you don't want to change it
// Defaults to 32
OptionSessionsCookieLength(val int)
// OptionSessionsGcDuration every how much duration(GcDuration) the memory should be clear for unused cookies (GcDuration)
// for example: time.Duration(2)*time.Hour. it will check every 2 hours if cookie hasn't be used for 2 hours,
// deletes it from backend memory until the user comes back, then the session continue to work as it was
//
// Default 2 hours
OptionSessionsGcDuration(val time.Duration)
// OptionSessionsDisableSubdomainPersistence set it to true in order dissallow your q subdomains to have access to the session cookie
// defaults to false
OptionSessionsDisableSubdomainPersistence(val bool)
// OptionWebsocketWriteTimeout time allowed to write a message to the connection.
// Default value is 15 * time.Second
OptionWebsocketWriteTimeout(val time.Duration)
// OptionWebsocketPongTimeout allowed to read the next pong message from the connection
// Default value is 60 * time.Second
OptionWebsocketPongTimeout(val time.Duration)
// OptionWebsocketPingPeriod send ping messages to the connection with this period. Must be less than PongTimeout
// Default value is (PongTimeout * 9) / 10
OptionWebsocketPingPeriod(val time.Duration)
// OptionWebsocketMaxMessageSize max message size allowed from connection
// Default value is 1024
OptionWebsocketMaxMessageSize(val int64)
// OptionWebsocketBinaryMessages 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
OptionWebsocketBinaryMessages(val bool)
// OptionWebsocketEndpoint 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.
OptionWebsocketEndpoint(val string)
// OptionWebsocketReadBufferSize is the buffer size for the underline reader
OptionWebsocketReadBufferSize(val int)
// OptionWebsocketWriteBufferSize is the buffer size for the underline writer
OptionWebsocketWriteBufferSize(val int)
// OptionTesterListeningAddr is the virtual server's listening addr (host)
// Default is "iris-go.com:1993"
OptionTesterListeningAddr(val string)
// OptionTesterExplicitURL If true then the url (should) be prepended manually, useful when want to test subdomains
// Default is false
OptionTesterExplicitURL(val bool)
// OptionTesterDebug if true then debug messages from the httpexpect will be shown when a test runs
// Default is false
OptionTesterDebug(val bool)
```
Now, some of you maybe use more than one server inside their iris instance/app, so you used the `iris.AddServer(config.Server{})`, which now becomes `iris.AddServer(iris.ServerConfiguration{})`, ServerConfiguration has also (optional) options to pass there and to `iris.ListenTo(OptionServerListeningAddr("mydomain.com"))`:
```go
// examples:
iris.AddServer(iris.OptionServerCertFile("file.cert"),iris.OptionServerKeyFile("file.key"))
iris.ListenTo(iris.OptionServerReadBufferSize(42000))
// or, old way but still valid:
iris.AddServer(iris.ServerConfiguration{ListeningAddr: "mydomain.com", CertFile: "file.cert", KeyFile: "file.key"})
iris.ListenTo(iris.ServerConfiguration{ReadBufferSize:42000, ListeningAddr: "mydomain.com"})
```
**List** of all Server's options:
```go
OptionServerListeningAddr(val string)
OptionServerCertFile(val string)
OptionServerKeyFile(val string)
// AutoTLS enable to get certifications from the Letsencrypt
// when this configuration field is true, the CertFile & KeyFile are empty, no need to provide a key.
//
// example: https://github.com/iris-contrib/examples/blob/master/letsencyrpt/main.go
OptionServerAutoTLS(val bool)
// Mode this is for unix only
OptionServerMode(val os.FileMode)
// OptionServerMaxRequestBodySize Maximum request body size.
//
// The server rejects requests with bodies exceeding this limit.
//
// By default request body size is 8MB.
OptionServerMaxRequestBodySize(val int)
// Per-connection buffer size for requests' reading.
// This also limits the maximum header size.
//
// Increase this buffer if your clients send multi-KB RequestURIs
// and/or multi-KB headers (for example, BIG cookies).
//
// Default buffer size is used if not set.
OptionServerReadBufferSize(val int)
// Per-connection buffer size for responses' writing.
//
// Default buffer size is used if not set.
OptionServerWriteBufferSize(val int)
// Maximum duration for reading the full request (including body).
//
// This also limits the maximum duration for idle keep-alive
// connections.
//
// By default request read timeout is unlimited.
OptionServerReadTimeout(val time.Duration)
// Maximum duration for writing the full response (including body).
//
// By default response write timeout is unlimited.
OptionServerWriteTimeout(val time.Duration)
// RedirectTo, defaults to empty, set it in order to override the station's handler and redirect all requests to this address which is of form(HOST:PORT or :PORT)
//
// NOTE: the http status is 'StatusMovedPermanently', means one-time-redirect(the browser remembers the new addr and goes to the new address without need to request something from this server
// which means that if you want to change this address you have to clear your browser's cache in order this to be able to change to the new addr.
//
// example: https://github.com/iris-contrib/examples/tree/master/multiserver_listening2
OptionServerRedirectTo(val string)
// OptionServerVirtual If this server is not really listens to a real host, it mostly used in order to achieve testing without system modifications
OptionServerVirtual(val bool)
// OptionServerVListeningAddr, can be used for both virtual = true or false,
// if it's setted to not empty, then the server's Host() will return this addr instead of the ListeningAddr.
// server's Host() is used inside global template helper funcs
// set it when you are sure you know what it does.
//
// Default is empty ""
OptionServerVListeningAddr(val string)
// OptionServerVScheme if setted to not empty value then all template's helper funcs prepends that as the url scheme instead of the real scheme
// server's .Scheme returns VScheme if not empty && differs from real scheme
//
// Default is empty ""
OptionServerVScheme(val string)
// OptionServerName the server's name, defaults to "iris".
// You're free to change it, but I will trust you to don't, this is the only setting whose somebody, like me, can see if iris web framework is used
OptionServerName(val string)
```
View all configuration fields and options by navigating to the [kataras/iris/configuration.go source file](https://github.com/kataras/iris/blob/master/configuration.go)
[Book](https://kataras.gitbooks.io/iris/content/configuration.html) & [Examples](https://github.com/iris-contrib/examples) are updated (website docs will be updated soon).
## 4.1.6 -> 4.1.7

View File

@ -18,7 +18,7 @@
<br/>
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%204.1.7%20-blue.svg?style=flat-square" alt="Releases"></a>
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%204.2.0%20-blue.svg?style=flat-square" alt="Releases"></a>
<a href="https://github.com/iris-contrib/examples"><img src="https://img.shields.io/badge/%20examples-repository-3362c2.svg?style=flat-square" alt="Examples"></a>
@ -178,7 +178,7 @@ I recommend writing your API tests using this new library, [httpexpect](https://
Versioning
------------
Current: **v4.1.7**
Current: **v4.2.0**
> Iris is an active project
@ -191,7 +191,7 @@ Read more about Semantic Versioning 2.0.0
Todo
------------
- [x] Use of the standard `log.Logger` instead of the `iris-contrib/logger`(colorful logger), make these changes to all middleware, examples and plugins.
- [ ] Implement, even, a better way to manage configuration/options, devs will be able to set their own custom options inside there. ` I'm thinking of something the last days, but it will have breaking changes. `
- [x] Implement, even, a better way to manage configuration/options, devs will be able to set their own custom options inside there. ` I'm thinking of something the last days, but it will have breaking changes. `
- [ ] Implement an internal updater, as requested [here](https://github.com/kataras/iris/issues/401).
Iris is a **Community-Driven** Project, waiting for your suggestions and [feature requests](https://github.com/kataras/iris/issues?utf8=%E2%9C%93&q=label%3A%22feature%20request%22)!
@ -221,7 +221,7 @@ License can be found [here](LICENSE).
[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-v4.1.7-blue.svg?style=flat-square
[Release Widget]: https://img.shields.io/badge/release-v4.2.0-blue.svg?style=flat-square
[Release]: https://github.com/kataras/iris/releases
[Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square
[Chat]: https://kataras.rocket.chat/channel/iris

View File

@ -1,18 +0,0 @@
// Package config defines the default settings and semantic variables
package config
import (
"time"
)
var (
// TimeFormat default time format for any kind of datetime parsing
TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
// StaticCacheDuration expiration duration for INACTIVE file handlers
StaticCacheDuration = 20 * time.Second
// CompressedFileSuffix is the suffix to add to the name of
// cached compressed file when using the .StaticFS function.
//
// Defaults to iris-fasthttp.gz
CompressedFileSuffix = "iris-fasthttp.gz"
)

View File

@ -1,163 +0,0 @@
package config
import (
"io"
"os"
"github.com/imdario/mergo"
)
// Default values for base Iris conf
const (
DefaultDisablePathCorrection = false
DefaultDisablePathEscape = false
DefaultCharset = "UTF-8"
DefaultLoggerPreffix = "[IRIS] "
)
var (
DefaultLoggerOut = os.Stdout
)
type (
// Iris configs for the station
Iris struct {
// DisablePathCorrection corrects and redirects the requested path to the registed path
// for example, if /home/ path is requested but no handler for this Route found,
// then the Router checks if /home handler exists, if yes,
// (permant)redirects the client to the correct path /home
//
// Default is false
DisablePathCorrection bool
// DisablePathEscape when is false then its escapes the path, the named parameters (if any).
// Change to true it if you want something like this https://github.com/kataras/iris/issues/135 to work
//
// When do you need to Disable(true) it:
// accepts parameters with slash '/'
// Request: http://localhost:8080/details/Project%2FDelta
// ctx.Param("project") returns the raw named parameter: Project%2FDelta
// which you can escape it manually with net/url:
// projectName, _ := url.QueryUnescape(c.Param("project").
// Look here: https://github.com/kataras/iris/issues/135 for more
//
// Default is false
DisablePathEscape bool
// DisableBanner outputs the iris banner at startup
//
// Default is false
DisableBanner bool
// LoggerOut is the destination for output
//
// defaults to os.Stdout
LoggerOut io.Writer
// LoggerOut is the logger's prefix to write at beginning of each line
//
// Defaults to [IRIS]
LoggerPreffix string
// ProfilePath a the route path, set it to enable http pprof tool
// Default is empty, if you set it to a $path, these routes will handled:
// $path/cmdline
// $path/profile
// $path/symbol
// $path/goroutine
// $path/heap
// $path/threadcreate
// $path/pprof/block
// for example if '/debug/pprof'
// http://yourdomain:PORT/debug/pprof/
// http://yourdomain:PORT/debug/pprof/cmdline
// http://yourdomain:PORT/debug/pprof/profile
// http://yourdomain:PORT/debug/pprof/symbol
// http://yourdomain:PORT/debug/pprof/goroutine
// http://yourdomain:PORT/debug/pprof/heap
// http://yourdomain:PORT/debug/pprof/threadcreate
// http://yourdomain:PORT/debug/pprof/pprof/block
// it can be a subdomain also, for example, if 'debug.'
// http://debug.yourdomain:PORT/
// http://debug.yourdomain:PORT/cmdline
// http://debug.yourdomain:PORT/profile
// http://debug.yourdomain:PORT/symbol
// http://debug.yourdomain:PORT/goroutine
// http://debug.yourdomain:PORT/heap
// http://debug.yourdomain:PORT/threadcreate
// http://debug.yourdomain:PORT/pprof/block
ProfilePath string
// DisableTemplateEngines set to true to disable loading the default template engine (html/template) and disallow the use of iris.UseEngine
// default is false
DisableTemplateEngines bool
// IsDevelopment iris will act like a developer, for example
// If true then re-builds the templates on each request
// default is false
IsDevelopment bool
// Charset character encoding for various rendering
// used for templates and the rest of the responses
// defaults to "UTF-8"
Charset string
// Gzip enables gzip compression on your Render actions, this includes any type of render, templates and pure/raw content
// If you don't want to enable it globaly, you could just use the third parameter on context.Render("myfileOrResponse", structBinding{}, iris.RenderOptions{"gzip": true})
// defaults to false
Gzip bool
// Sessions contains the configs for sessions
Sessions Sessions
// Websocket contains the configs for Websocket's server integration
Websocket *Websocket
// Tester contains the configs for the test framework, so far we have only one because all test framework's configs are setted by the iris itself
// You can find example on the https://github.com/kataras/iris/glob/master/context_test.go
Tester Tester
}
)
// Default returns the default configuration for the Iris staton
func Default() Iris {
return Iris{
DisablePathCorrection: DefaultDisablePathCorrection,
DisablePathEscape: DefaultDisablePathEscape,
DisableBanner: false,
LoggerOut: DefaultLoggerOut,
LoggerPreffix: DefaultLoggerPreffix,
DisableTemplateEngines: false,
IsDevelopment: false,
Charset: DefaultCharset,
Gzip: false,
ProfilePath: "",
Sessions: DefaultSessions(),
Websocket: DefaultWebsocket(),
Tester: DefaultTester(),
}
}
// Merge merges the default with the given config and returns the result
// receives an array because the func caller is variadic
func (c Iris) Merge(cfg []Iris) (config Iris) {
// I tried to make it more generic with interfaces for all configs, inside config.go but it fails,
// so do it foreach configuration np they aint so much...
if cfg != nil && len(cfg) > 0 {
config = cfg[0]
mergo.Merge(&config, c)
} else {
_default := c
config = _default
}
return
}
// MergeSingle merges the default with the given config and returns the result
func (c Iris) MergeSingle(cfg Iris) (config Iris) {
config = cfg
mergo.Merge(&config, c)
return
}

View File

@ -1,185 +0,0 @@
package config
import (
"os"
"strconv"
"strings"
"time"
"github.com/imdario/mergo"
"github.com/valyala/fasthttp"
)
// Default values for base Server conf
const (
// DefaultServerHostname returns the default hostname which is 0.0.0.0
DefaultServerHostname = "0.0.0.0"
// DefaultServerPort returns the default port which is 8080
DefaultServerPort = 8080
// DefaultMaxRequestBodySize is 8MB
DefaultMaxRequestBodySize = 2 * fasthttp.DefaultMaxRequestBodySize
// Per-connection buffer size for requests' reading.
// This also limits the maximum header size.
//
// Increase this buffer if your clients send multi-KB RequestURIs
// and/or multi-KB headers (for example, BIG cookies).
//
// Default buffer size is 8MB
DefaultReadBufferSize = 8096
// Per-connection buffer size for responses' writing.
//
// Default buffer size is 8MB
DefaultWriteBufferSize = 8096
// DefaultServerName the response header of the 'Server' value when writes to the client
DefaultServerName = "iris"
)
var (
// DefaultServerAddr the default server addr which is: 0.0.0.0:8080
DefaultServerAddr = DefaultServerHostname + ":" + strconv.Itoa(DefaultServerPort)
)
// Server used inside server for listening
type Server struct {
// ListenningAddr the addr that server listens to
ListeningAddr string
CertFile string
KeyFile string
// AutoTLS enable to get certifications from the Letsencrypt
// when this configuration field is true, the CertFile & KeyFile are empty, no need to provide a key.
//
// example: https://github.com/iris-contrib/examples/blob/master/letsencyrpt/main.go
AutoTLS bool
// Mode this is for unix only
Mode os.FileMode
// MaxRequestBodySize Maximum request body size.
//
// The server rejects requests with bodies exceeding this limit.
//
// By default request body size is 8MB.
MaxRequestBodySize int
// Per-connection buffer size for requests' reading.
// This also limits the maximum header size.
//
// Increase this buffer if your clients send multi-KB RequestURIs
// and/or multi-KB headers (for example, BIG cookies).
//
// Default buffer size is used if not set.
ReadBufferSize int
// Per-connection buffer size for responses' writing.
//
// Default buffer size is used if not set.
WriteBufferSize int
// Maximum duration for reading the full request (including body).
//
// This also limits the maximum duration for idle keep-alive
// connections.
//
// By default request read timeout is unlimited.
ReadTimeout time.Duration
// Maximum duration for writing the full response (including body).
//
// By default response write timeout is unlimited.
WriteTimeout time.Duration
// RedirectTo, defaults to empty, set it in order to override the station's handler and redirect all requests to this address which is of form(HOST:PORT or :PORT)
//
// NOTE: the http status is 'StatusMovedPermanently', means one-time-redirect(the browser remembers the new addr and goes to the new address without need to request something from this server
// which means that if you want to change this address you have to clear your browser's cache in order this to be able to change to the new addr.
//
// example: https://github.com/iris-contrib/examples/tree/master/multiserver_listening2
RedirectTo string
// Virtual If this server is not really listens to a real host, it mostly used in order to achieve testing without system modifications
Virtual bool
// VListeningAddr, can be used for both virtual = true or false,
// if it's setted to not empty, then the server's Host() will return this addr instead of the ListeningAddr.
// server's Host() is used inside global template helper funcs
// set it when you are sure you know what it does.
//
// Default is empty ""
VListeningAddr string
// VScheme if setted to not empty value then all template's helper funcs prepends that as the url scheme instead of the real scheme
// server's .Scheme returns VScheme if not empty && differs from real scheme
//
// Default is empty ""
VScheme string
// Name the server's name, defaults to "iris".
// You're free to change it, but I will trust you to don't, this is the only setting whose somebody, like me, can see if iris web framework is used
Name string
}
// ServerParseAddr parses the listening addr and returns this
func ServerParseAddr(listeningAddr string) string {
// check if addr has :port, if not do it +:80 ,we need the hostname for many cases
a := listeningAddr
if a == "" {
// check for os environments
if oshost := os.Getenv("HOST"); oshost != "" {
a = oshost
} else if oshost := os.Getenv("ADDR"); oshost != "" {
a = oshost
} else if osport := os.Getenv("PORT"); osport != "" {
a = ":" + osport
}
if a == "" {
a = DefaultServerAddr
}
}
if portIdx := strings.IndexByte(a, ':'); portIdx == 0 {
// if contains only :port ,then the : is the first letter, so we dont have setted a hostname, lets set it
a = DefaultServerHostname + a
}
if portIdx := strings.IndexByte(a, ':'); portIdx < 0 {
// missing port part, add it
a = a + ":80"
}
return a
}
// DefaultServer returns the default configs for the server
func DefaultServer() Server {
return Server{
ListeningAddr: DefaultServerAddr,
Name: DefaultServerName,
MaxRequestBodySize: DefaultMaxRequestBodySize,
ReadBufferSize: DefaultReadBufferSize,
WriteBufferSize: DefaultWriteBufferSize,
RedirectTo: "",
Virtual: false,
VListeningAddr: "",
VScheme: "",
}
}
// Merge merges the default with the given config and returns the result
func (c Server) Merge(cfg []Server) (config Server) {
if len(cfg) > 0 {
config = cfg[0]
mergo.Merge(&config, c)
} else {
_default := c
config = _default
}
return
}
// MergeSingle merges the default with the given config and returns the result
func (c Server) MergeSingle(cfg Server) (config Server) {
config = cfg
mergo.Merge(&config, c)
return
}

View File

@ -1,68 +0,0 @@
package config
import (
"github.com/imdario/mergo"
"github.com/kataras/go-sessions"
"time"
)
var (
universe time.Time // 0001-01-01 00:00:00 +0000 UTC
// CookieExpireNever the default cookie's life for sessions, unlimited (23 years)
CookieExpireNever = time.Now().AddDate(23, 0, 0)
)
const (
// DefaultCookieName the secret cookie's name for sessions
DefaultCookieName = "irissessionid"
// DefaultSessionGcDuration is the default Session Manager's GCDuration , which is 2 hours
DefaultSessionGcDuration = time.Duration(2) * time.Hour
// DefaultCookieLength is the default Session Manager's CookieLength, which is 32
DefaultCookieLength = 32
)
// Sessions the configuration for sessions
// has 6 fields
// first is the cookieName, the session's name (string) ["mysessionsecretcookieid"]
// second enable if you want to decode the cookie's key also
// third is the time which the client's cookie expires
// forth is the cookie length (sessionid) int, defaults to 32, do not change if you don't have any reason to do
// fifth is the gcDuration (time.Duration) when this time passes it removes the unused sessions from the memory until the user come back
// sixth is the DisableSubdomainPersistence which you can set it to true in order dissallow your q subdomains to have access to the session cook
//
type Sessions sessions.Config
// DefaultSessions the default configs for Sessions
func DefaultSessions() Sessions {
return Sessions{
Cookie: DefaultCookieName,
CookieLength: DefaultCookieLength,
DecodeCookie: false,
Expires: 0,
GcDuration: DefaultSessionGcDuration,
DisableSubdomainPersistence: false,
}
}
// Merge merges the default with the given config and returns the result
func (c Sessions) Merge(cfg []Sessions) (config Sessions) {
if len(cfg) > 0 {
config = cfg[0]
mergo.Merge(&config, c)
} else {
_default := c
config = _default
}
return
}
// MergeSingle merges the default with the given config and returns the result
func (c Sessions) MergeSingle(cfg Sessions) (config Sessions) {
config = cfg
mergo.Merge(&config, c)
return
}

View File

@ -1,14 +0,0 @@
package config
// Tester configuration
type Tester struct {
ListeningAddr string
ExplicitURL bool
Debug bool
}
// DefaultTester returns the default configuration for a tester
// the ListeningAddr is used as virtual only when no running server is founded
func DefaultTester() Tester {
return Tester{ListeningAddr: "iris-go.com:1993", ExplicitURL: false, Debug: false}
}

View File

@ -1,88 +0,0 @@
package config
import (
"time"
"github.com/imdario/mergo"
)
const (
// DefaultWriteTimeout 15 * time.Second
DefaultWriteTimeout = 15 * time.Second
// DefaultPongTimeout 60 * time.Second
DefaultPongTimeout = 60 * time.Second
// DefaultPingPeriod (DefaultPongTimeout * 9) / 10
DefaultPingPeriod = (DefaultPongTimeout * 9) / 10
// DefaultMaxMessageSize 1024
DefaultMaxMessageSize = 1024
)
//
// Websocket the config contains options for the ../websocket.go
type Websocket 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
// Headers the response headers before upgrader
// Default is empty
Headers map[string]string
// ReadBufferSize is the buffer size for the underline reader
ReadBufferSize int
// WriteBufferSize is the buffer size for the underline writer
WriteBufferSize int
}
// DefaultWebsocket returns the default config for iris-ws websocket package
func DefaultWebsocket() *Websocket {
return &Websocket{
WriteTimeout: DefaultWriteTimeout,
PongTimeout: DefaultPongTimeout,
PingPeriod: DefaultPingPeriod,
MaxMessageSize: DefaultMaxMessageSize,
BinaryMessages: false,
ReadBufferSize: 4096,
WriteBufferSize: 4096,
Headers: make(map[string]string, 0),
Endpoint: "",
}
}
// Merge merges the default with the given config and returns the result
func (c *Websocket) Merge(cfg []*Websocket) (config *Websocket) {
if len(cfg) > 0 {
config = cfg[0]
mergo.Merge(config, c)
} else {
_default := c
config = _default
}
return
}
// MergeSingle merges the default with the given config and returns the result
func (c *Websocket) MergeSingle(cfg *Websocket) (config *Websocket) {
config = cfg
mergo.Merge(config, c)
return
}

859
configuration.go Normal file
View File

@ -0,0 +1,859 @@
package iris
import (
"github.com/imdario/mergo"
"github.com/kataras/go-options"
"github.com/kataras/go-sessions"
"github.com/valyala/fasthttp"
"io"
"os"
"strconv"
"strings"
"time"
)
type (
// OptionSetter sets a configuration field to the main configuration
// used to help developers to write less and configure only what they really want and nothing else
// example:
// iris.New(iris.Configuration{Sessions:iris.SessionConfiguration{Cookie:"mysessionid"}, Websocket: iris.WebsocketConfiguration{Endpoint:"/my_endpoint"}})
// now can be done also by using iris.Option$FIELD:
// iris.New(irisOptionSessionsCookie("mycookieid"),iris.OptionWebsocketEndpoint("my_endpoint"))
// benefits:
// 1. user/dev have no worries what option to pass, he/she can just press iris.Option and all options should be shown to her/his editor's autocomplete-popup window
// 2. can be passed with any order
// 3. Can override previous configuration
OptionSetter interface {
// Set receives a pointer to the global Configuration type and does the job of filling it
Set(c *Configuration)
}
// OptionSet implements the OptionSetter
OptionSet func(c *Configuration)
)
// Set is the func which makes the OptionSet an OptionSetter, this is used mostly
func (o OptionSet) Set(c *Configuration) {
o(c)
}
// Configuration the whole configuration for an iris instance ($instance.Config) or global iris instance (iris.Config)
// these can be passed via options also, look at the top of this file(configuration.go)
//
// Configuration is also implements the OptionSet so it's a valid option itself, this is briliant enough
type Configuration struct {
// DisablePathCorrection corrects and redirects the requested path to the registed path
// for example, if /home/ path is requested but no handler for this Route found,
// then the Router checks if /home handler exists, if yes,
// (permant)redirects the client to the correct path /home
//
// Default is false
DisablePathCorrection bool
// DisablePathEscape when is false then its escapes the path, the named parameters (if any).
// Change to true it if you want something like this https://github.com/kataras/iris/issues/135 to work
//
// When do you need to Disable(true) it:
// accepts parameters with slash '/'
// Request: http://localhost:8080/details/Project%2FDelta
// ctx.Param("project") returns the raw named parameter: Project%2FDelta
// which you can escape it manually with net/url:
// projectName, _ := url.QueryUnescape(c.Param("project").
// Look here: https://github.com/kataras/iris/issues/135 for more
//
// Default is false
DisablePathEscape bool
// DisableBanner outputs the iris banner at startup
//
// Default is false
DisableBanner bool
// LoggerOut is the destination for output
//
// Default is os.Stdout
LoggerOut io.Writer
// LoggerPreffix is the logger's prefix to write at beginning of each line
//
// Default is [IRIS]
LoggerPreffix string
// ProfilePath a the route path, set it to enable http pprof tool
// Default is empty, if you set it to a $path, these routes will handled:
// $path/cmdline
// $path/profile
// $path/symbol
// $path/goroutine
// $path/heap
// $path/threadcreate
// $path/pprof/block
// for example if '/debug/pprof'
// http://yourdomain:PORT/debug/pprof/
// http://yourdomain:PORT/debug/pprof/cmdline
// http://yourdomain:PORT/debug/pprof/profile
// http://yourdomain:PORT/debug/pprof/symbol
// http://yourdomain:PORT/debug/pprof/goroutine
// http://yourdomain:PORT/debug/pprof/heap
// http://yourdomain:PORT/debug/pprof/threadcreate
// http://yourdomain:PORT/debug/pprof/pprof/block
// it can be a subdomain also, for example, if 'debug.'
// http://debug.yourdomain:PORT/
// http://debug.yourdomain:PORT/cmdline
// http://debug.yourdomain:PORT/profile
// http://debug.yourdomain:PORT/symbol
// http://debug.yourdomain:PORT/goroutine
// http://debug.yourdomain:PORT/heap
// http://debug.yourdomain:PORT/threadcreate
// http://debug.yourdomain:PORT/pprof/block
ProfilePath string
// DisableTemplateEngines set to true to disable loading the default template engine (html/template) and disallow the use of iris.UseEngine
// default is false
DisableTemplateEngines bool
// IsDevelopment iris will act like a developer, for example
// If true then re-builds the templates on each request
// default is false
IsDevelopment bool
// TimeFormat time format for any kind of datetime parsing
TimeFormat string
// Charset character encoding for various rendering
// used for templates and the rest of the responses
// defaults to "UTF-8"
Charset string
// Gzip enables gzip compression on your Render actions, this includes any type of render, templates and pure/raw content
// If you don't want to enable it globaly, you could just use the third parameter on context.Render("myfileOrResponse", structBinding{}, iris.RenderOptions{"gzip": true})
// defaults to false
Gzip bool
// Sessions contains the configs for sessions
Sessions SessionsConfiguration
// Websocket contains the configs for Websocket's server integration
Websocket WebsocketConfiguration
// Tester contains the configs for the test framework, so far we have only one because all test framework's configs are setted by the iris itself
// You can find example on the https://github.com/kataras/iris/glob/master/context_test.go
Tester TesterConfiguration
// Other are the custom, dynamic options, can be empty
// this fill used only by you to set any app's options you want
// for each of an Iris instance
Other options.Options
}
// Set implements the OptionSetter
func (c Configuration) Set(main *Configuration) {
mergo.MergeWithOverwrite(main, c)
}
// All options starts with "Option" preffix in order to be easier to find what dev searching for
var (
// OptionDisablePathCorrection corrects and redirects the requested path to the registed path
// for example, if /home/ path is requested but no handler for this Route found,
// then the Router checks if /home handler exists, if yes,
// (permant)redirects the client to the correct path /home
//
// Default is false
OptionDisablePathCorrection = func(val bool) OptionSet {
return func(c *Configuration) {
c.DisablePathCorrection = val
}
}
// OptionDisablePathEscape when is false then its escapes the path, the named parameters (if any).
OptionDisablePathEscape = func(val bool) OptionSet {
return func(c *Configuration) {
c.DisablePathEscape = val
}
}
// OptionDisableBanner outputs the iris banner at startup
//
// Default is false
OptionDisableBanner = func(val bool) OptionSet {
return func(c *Configuration) {
c.DisableBanner = val
}
}
// OptionLoggerOut is the destination for output
//
// Default is os.Stdout
OptionLoggerOut = func(val io.Writer) OptionSet {
return func(c *Configuration) {
c.LoggerOut = val
}
}
// OptionLoggerPreffix is the logger's prefix to write at beginning of each line
//
// Default is [IRIS]
OptionLoggerPreffix = func(val string) OptionSet {
return func(c *Configuration) {
c.LoggerPreffix = val
}
}
// OptionProfilePath a the route path, set it to enable http pprof tool
// Default is empty, if you set it to a $path, these routes will handled:
OptionProfilePath = func(val string) OptionSet {
return func(c *Configuration) {
c.ProfilePath = val
}
}
// OptionDisableTemplateEngines set to true to disable loading the default template engine (html/template) and disallow the use of iris.UseEngine
// Default is false
OptionDisableTemplateEngines = func(val bool) OptionSet {
return func(c *Configuration) {
c.DisableTemplateEngines = val
}
}
// OptionIsDevelopment iris will act like a developer, for example
// If true then re-builds the templates on each request
// Default is false
OptionIsDevelopment = func(val bool) OptionSet {
return func(c *Configuration) {
c.IsDevelopment = val
}
}
// OptionTimeFormat time format for any kind of datetime parsing
OptionTimeFormat = func(val string) OptionSet {
return func(c *Configuration) {
c.TimeFormat = val
}
}
// OptionCharset character encoding for various rendering
// used for templates and the rest of the responses
// Default is "UTF-8"
OptionCharset = func(val string) OptionSet {
return func(c *Configuration) {
c.Charset = val
}
}
// OptionGzip enables gzip compression on your Render actions, this includes any type of render, templates and pure/raw content
// If you don't want to enable it globaly, you could just use the third parameter on context.Render("myfileOrResponse", structBinding{}, iris.RenderOptions{"gzip": true})
// Default is false
OptionGzip = func(val bool) OptionSet {
return func(c *Configuration) {
c.Gzip = val
}
}
// OptionOther are the custom, dynamic options, can be empty
// this fill used only by you to set any app's options you want
// for each of an Iris instance
OptionOther = func(val ...options.Options) OptionSet {
opts := options.Options{}
for _, opt := range val {
for k, v := range opt {
opts[k] = v
}
}
return func(c *Configuration) {
c.Other = opts
}
}
)
var (
// DefaultTimeFormat default time format for any kind of datetime parsing
DefaultTimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
// StaticCacheDuration expiration duration for INACTIVE file handlers, it's a global configuration field to all iris instances
StaticCacheDuration = 20 * time.Second
// CompressedFileSuffix is the suffix to add to the name of
// cached compressed file when using the .StaticFS function.
//
// Defaults to iris-fasthttp.gz
CompressedFileSuffix = "iris-fasthttp.gz"
)
// Default values for base Iris conf
const (
DefaultDisablePathCorrection = false
DefaultDisablePathEscape = false
DefaultCharset = "UTF-8"
DefaultLoggerPreffix = "[IRIS] "
)
var (
// DefaultLoggerOut is the default logger's output
DefaultLoggerOut = os.Stdout
)
// DefaultConfiguration returns the default configuration for an Iris station, fills the main Configuration
func DefaultConfiguration() Configuration {
return Configuration{
DisablePathCorrection: DefaultDisablePathCorrection,
DisablePathEscape: DefaultDisablePathEscape,
DisableBanner: false,
LoggerOut: DefaultLoggerOut,
LoggerPreffix: DefaultLoggerPreffix,
DisableTemplateEngines: false,
IsDevelopment: false,
TimeFormat: DefaultTimeFormat,
Charset: DefaultCharset,
Gzip: false,
ProfilePath: "",
Sessions: DefaultSessionsConfiguration(),
Websocket: DefaultWebsocketConfiguration(),
Tester: DefaultTesterConfiguration(),
Other: options.Options{},
}
}
// SessionsConfiguration the configuration for sessions
// has 6 fields
// first is the cookieName, the session's name (string) ["mysessionsecretcookieid"]
// second enable if you want to decode the cookie's key also
// third is the time which the client's cookie expires
// forth is the cookie length (sessionid) int, defaults to 32, do not change if you don't have any reason to do
// fifth is the gcDuration (time.Duration) when this time passes it removes the unused sessions from the memory until the user come back
// sixth is the DisableSubdomainPersistence which you can set it to true in order dissallow your q subdomains to have access to the session cook
type SessionsConfiguration sessions.Config
var (
// OptionSessionsCookie string, the session's client cookie name, for example: "qsessionid"
OptionSessionsCookie = func(val string) OptionSet {
return func(c *Configuration) {
c.Sessions.Cookie = val
}
}
// OptionSessionsDecodeCookie set it to true to decode the cookie key with base64 URLEncoding
// Defaults to false
OptionSessionsDecodeCookie = func(val bool) OptionSet {
return func(c *Configuration) {
c.Sessions.DecodeCookie = val
}
}
// OptionSessionsExpires the duration of which the cookie must expires (created_time.Add(Expires)).
// If you want to delete the cookie when the browser closes, set it to -1 but in this case, the server side's session duration is up to GcDuration
//
// Default infinitive/unlimited life duration(0)
OptionSessionsExpires = func(val time.Duration) OptionSet {
return func(c *Configuration) {
c.Sessions.Expires = val
}
}
// OptionSessionsCookieLength the length of the sessionid's cookie's value, let it to 0 if you don't want to change it
// Defaults to 32
OptionSessionsCookieLength = func(val int) OptionSet {
return func(c *Configuration) {
c.Sessions.CookieLength = val
}
}
// OptionSessionsGcDuration every how much duration(GcDuration) the memory should be clear for unused cookies (GcDuration)
// for example: time.Duration(2)*time.Hour. it will check every 2 hours if cookie hasn't be used for 2 hours,
// deletes it from backend memory until the user comes back, then the session continue to work as it was
//
// Default 2 hours
OptionSessionsGcDuration = func(val time.Duration) OptionSet {
return func(c *Configuration) {
c.Sessions.GcDuration = val
}
}
// OptionSessionsDisableSubdomainPersistence set it to true in order dissallow your q subdomains to have access to the session cookie
// defaults to false
OptionSessionsDisableSubdomainPersistence = func(val bool) OptionSet {
return func(c *Configuration) {
c.Sessions.DisableSubdomainPersistence = val
}
}
)
var (
universe time.Time // 0001-01-01 00:00:00 +0000 UTC
// CookieExpireNever the default cookie's life for sessions, unlimited (23 years)
CookieExpireNever = time.Now().AddDate(23, 0, 0)
)
const (
// DefaultCookieName the secret cookie's name for sessions
DefaultCookieName = "irissessionid"
// DefaultSessionGcDuration is the default Session Manager's GCDuration , which is 2 hours
DefaultSessionGcDuration = time.Duration(2) * time.Hour
// DefaultCookieLength is the default Session Manager's CookieLength, which is 32
DefaultCookieLength = 32
)
// DefaultSessionsConfiguration the default configs for Sessions
func DefaultSessionsConfiguration() SessionsConfiguration {
return SessionsConfiguration{
Cookie: DefaultCookieName,
CookieLength: DefaultCookieLength,
DecodeCookie: false,
Expires: 0,
GcDuration: DefaultSessionGcDuration,
DisableSubdomainPersistence: false,
}
}
// WebsocketConfiguration the config contains options for the Websocket main config field
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
}
var (
// OptionWebsocketWriteTimeout time allowed to write a message to the connection.
// Default value is 15 * time.Second
OptionWebsocketWriteTimeout = func(val time.Duration) OptionSet {
return func(c *Configuration) {
c.Websocket.WriteTimeout = val
}
}
// OptionWebsocketPongTimeout allowed to read the next pong message from the connection
// Default value is 60 * time.Second
OptionWebsocketPongTimeout = func(val time.Duration) OptionSet {
return func(c *Configuration) {
c.Websocket.PongTimeout = val
}
}
// OptionWebsocketPingPeriod send ping messages to the connection with this period. Must be less than PongTimeout
// Default value is (PongTimeout * 9) / 10
OptionWebsocketPingPeriod = func(val time.Duration) OptionSet {
return func(c *Configuration) {
c.Websocket.PingPeriod = val
}
}
// OptionWebsocketMaxMessageSize max message size allowed from connection
// Default value is 1024
OptionWebsocketMaxMessageSize = func(val int64) OptionSet {
return func(c *Configuration) {
c.Websocket.MaxMessageSize = val
}
}
// OptionWebsocketBinaryMessages 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
OptionWebsocketBinaryMessages = func(val bool) OptionSet {
return func(c *Configuration) {
c.Websocket.BinaryMessages = val
}
}
// OptionWebsocketEndpoint 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.
OptionWebsocketEndpoint = func(val string) OptionSet {
return func(c *Configuration) {
c.Websocket.Endpoint = val
}
}
// OptionWebsocketReadBufferSize is the buffer size for the underline reader
OptionWebsocketReadBufferSize = func(val int) OptionSet {
return func(c *Configuration) {
c.Websocket.ReadBufferSize = val
}
}
// OptionWebsocketWriteBufferSize is the buffer size for the underline writer
OptionWebsocketWriteBufferSize = func(val int) OptionSet {
return func(c *Configuration) {
c.Websocket.WriteBufferSize = val
}
}
)
const (
// DefaultWriteTimeout 15 * time.Second
DefaultWriteTimeout = 15 * time.Second
// DefaultPongTimeout 60 * time.Second
DefaultPongTimeout = 60 * time.Second
// DefaultPingPeriod (DefaultPongTimeout * 9) / 10
DefaultPingPeriod = (DefaultPongTimeout * 9) / 10
// DefaultMaxMessageSize 1024
DefaultMaxMessageSize = 1024
)
// DefaultWebsocketConfiguration returns the default config for iris-ws websocket package
func DefaultWebsocketConfiguration() WebsocketConfiguration {
return WebsocketConfiguration{
WriteTimeout: DefaultWriteTimeout,
PongTimeout: DefaultPongTimeout,
PingPeriod: DefaultPingPeriod,
MaxMessageSize: DefaultMaxMessageSize,
BinaryMessages: false,
ReadBufferSize: 4096,
WriteBufferSize: 4096,
Endpoint: "",
}
}
// TesterConfiguration configuration used inside main config field 'Tester'
type TesterConfiguration struct {
// ListeningAddr is the virtual server's listening addr (host)
// Default is "iris-go.com:1993"
ListeningAddr string
// ExplicitURL If true then the url (should) be prepended manually, useful when want to test subdomains
// Default is false
ExplicitURL bool
// Debug if true then debug messages from the httpexpect will be shown when a test runs
// Default is false
Debug bool
}
var (
// OptionTesterListeningAddr is the virtual server's listening addr (host)
// Default is "iris-go.com:1993"
OptionTesterListeningAddr = func(val string) OptionSet {
return func(c *Configuration) {
c.Tester.ListeningAddr = val
}
}
// OptionTesterExplicitURL If true then the url (should) be prepended manually, useful when want to test subdomains
// Default is false
OptionTesterExplicitURL = func(val bool) OptionSet {
return func(c *Configuration) {
c.Tester.ExplicitURL = val
}
}
// OptionTesterDebug if true then debug messages from the httpexpect will be shown when a test runs
// Default is false
OptionTesterDebug = func(val bool) OptionSet {
return func(c *Configuration) {
c.Tester.Debug = val
}
}
)
// DefaultTesterConfiguration returns the default configuration for a tester
// the ListeningAddr is used as virtual only when no running server is founded
func DefaultTesterConfiguration() TesterConfiguration {
return TesterConfiguration{ListeningAddr: "iris-go.com:1993", ExplicitURL: false, Debug: false}
}
// ServerConfiguration is the configuration which is used inside iris' server(s) for listening to
type ServerConfiguration struct {
// ListenningAddr the addr that server listens to
ListeningAddr string
CertFile string
KeyFile string
// AutoTLS enable to get certifications from the Letsencrypt
// when this configuration field is true, the CertFile & KeyFile are empty, no need to provide a key.
//
// example: https://github.com/iris-contrib/examples/blob/master/letsencyrpt/main.go
AutoTLS bool
// Mode this is for unix only
Mode os.FileMode
// MaxRequestBodySize Maximum request body size.
//
// The server rejects requests with bodies exceeding this limit.
//
// By default request body size is 8MB.
MaxRequestBodySize int
// Per-connection buffer size for requests' reading.
// This also limits the maximum header size.
//
// Increase this buffer if your clients send multi-KB RequestURIs
// and/or multi-KB headers (for example, BIG cookies).
//
// Default buffer size is used if not set.
ReadBufferSize int
// Per-connection buffer size for responses' writing.
//
// Default buffer size is used if not set.
WriteBufferSize int
// Maximum duration for reading the full request (including body).
//
// This also limits the maximum duration for idle keep-alive
// connections.
//
// By default request read timeout is unlimited.
ReadTimeout time.Duration
// Maximum duration for writing the full response (including body).
//
// By default response write timeout is unlimited.
WriteTimeout time.Duration
// RedirectTo, defaults to empty, set it in order to override the station's handler and redirect all requests to this address which is of form(HOST:PORT or :PORT)
//
// NOTE: the http status is 'StatusMovedPermanently', means one-time-redirect(the browser remembers the new addr and goes to the new address without need to request something from this server
// which means that if you want to change this address you have to clear your browser's cache in order this to be able to change to the new addr.
//
// example: https://github.com/iris-contrib/examples/tree/master/multiserver_listening2
RedirectTo string
// Virtual If this server is not really listens to a real host, it mostly used in order to achieve testing without system modifications
Virtual bool
// VListeningAddr, can be used for both virtual = true or false,
// if it's setted to not empty, then the server's Host() will return this addr instead of the ListeningAddr.
// server's Host() is used inside global template helper funcs
// set it when you are sure you know what it does.
//
// Default is empty ""
VListeningAddr string
// VScheme if setted to not empty value then all template's helper funcs prepends that as the url scheme instead of the real scheme
// server's .Scheme returns VScheme if not empty && differs from real scheme
//
// Default is empty ""
VScheme string
// Name the server's name, defaults to "iris".
// You're free to change it, but I will trust you to don't, this is the only setting whose somebody, like me, can see if iris web framework is used
Name string
}
// note: ServerConfiguration is the only one config which has its own option setter because
// it's independent from a specific iris instance:
// same server can run on multi iris instance
// one iris instance/station can have and listening to more than one server.
// OptionServerSettter server configuration option setter
type OptionServerSettter interface {
Set(c *ServerConfiguration)
}
// OptionServerSet is the func which implements the OptionServerSettter, this is used widely
type OptionServerSet func(c *ServerConfiguration)
// Set is the func which makes OptionServerSet implements the OptionServerSettter
func (o OptionServerSet) Set(c *ServerConfiguration) {
o(c)
}
// Set implements the OptionServerSettter to the ServerConfiguration
func (c ServerConfiguration) Set(main *ServerConfiguration) {
mergo.MergeWithOverwrite(main, c)
}
// Options for ServerConfiguration
var (
OptionServerListeningAddr = func(val string) OptionServerSet {
return func(c *ServerConfiguration) {
c.ListeningAddr = val
}
}
OptionServerCertFile = func(val string) OptionServerSet {
return func(c *ServerConfiguration) {
c.CertFile = val
}
}
OptionServerKeyFile = func(val string) OptionServerSet {
return func(c *ServerConfiguration) {
c.KeyFile = val
}
}
// AutoTLS enable to get certifications from the Letsencrypt
// when this configuration field is true, the CertFile & KeyFile are empty, no need to provide a key.
//
// example: https://github.com/iris-contrib/examples/blob/master/letsencyrpt/main.go
OptionServerAutoTLS = func(val bool) OptionServerSet {
return func(c *ServerConfiguration) {
c.AutoTLS = val
}
}
// Mode this is for unix only
OptionServerMode = func(val os.FileMode) OptionServerSet {
return func(c *ServerConfiguration) {
c.Mode = val
}
}
// OptionServerMaxRequestBodySize Maximum request body size.
//
// The server rejects requests with bodies exceeding this limit.
//
// By default request body size is 8MB.
OptionServerMaxRequestBodySize = func(val int) OptionServerSet {
return func(c *ServerConfiguration) {
c.MaxRequestBodySize = val
}
}
// Per-connection buffer size for requests' reading.
// This also limits the maximum header size.
//
// Increase this buffer if your clients send multi-KB RequestURIs
// and/or multi-KB headers (for example, BIG cookies).
//
// Default buffer size is used if not set.
OptionServerReadBufferSize = func(val int) OptionServerSet {
return func(c *ServerConfiguration) {
c.ReadBufferSize = val
}
}
// Per-connection buffer size for responses' writing.
//
// Default buffer size is used if not set.
OptionServerWriteBufferSize = func(val int) OptionServerSet {
return func(c *ServerConfiguration) {
c.WriteBufferSize = val
}
}
// Maximum duration for reading the full request (including body).
//
// This also limits the maximum duration for idle keep-alive
// connections.
//
// By default request read timeout is unlimited.
OptionServerReadTimeout = func(val time.Duration) OptionServerSet {
return func(c *ServerConfiguration) {
c.ReadTimeout = val
}
}
// Maximum duration for writing the full response (including body).
//
// By default response write timeout is unlimited.
OptionServerWriteTimeout = func(val time.Duration) OptionServerSet {
return func(c *ServerConfiguration) {
c.WriteTimeout = val
}
}
// RedirectTo, defaults to empty, set it in order to override the station's handler and redirect all requests to this address which is of form(HOST:PORT or :PORT)
//
// NOTE: the http status is 'StatusMovedPermanently', means one-time-redirect(the browser remembers the new addr and goes to the new address without need to request something from this server
// which means that if you want to change this address you have to clear your browser's cache in order this to be able to change to the new addr.
//
// example: https://github.com/iris-contrib/examples/tree/master/multiserver_listening2
OptionServerRedirectTo = func(val string) OptionServerSet {
return func(c *ServerConfiguration) {
c.RedirectTo = val
}
}
// OptionServerVirtual If this server is not really listens to a real host, it mostly used in order to achieve testing without system modifications
OptionServerVirtual = func(val bool) OptionServerSet {
return func(c *ServerConfiguration) {
c.Virtual = val
}
}
// OptionServerVListeningAddr, can be used for both virtual = true or false,
// if it's setted to not empty, then the server's Host() will return this addr instead of the ListeningAddr.
// server's Host() is used inside global template helper funcs
// set it when you are sure you know what it does.
//
// Default is empty ""
OptionServerVListeningAddr = func(val string) OptionServerSet {
return func(c *ServerConfiguration) {
c.VListeningAddr = val
}
}
// OptionServerVScheme if setted to not empty value then all template's helper funcs prepends that as the url scheme instead of the real scheme
// server's .Scheme returns VScheme if not empty && differs from real scheme
//
// Default is empty ""
OptionServerVScheme = func(val string) OptionServerSet {
return func(c *ServerConfiguration) {
c.VScheme = val
}
}
// OptionServerName the server's name, defaults to "iris".
// You're free to change it, but I will trust you to don't, this is the only setting whose somebody, like me, can see if iris web framework is used
OptionServerName = func(val string) OptionServerSet {
return func(c *ServerConfiguration) {
c.ListeningAddr = val
}
}
)
// ServerParseAddr parses the listening addr and returns this
func ServerParseAddr(listeningAddr string) string {
// check if addr has :port, if not do it +:80 ,we need the hostname for many cases
a := listeningAddr
if a == "" {
// check for os environments
if oshost := os.Getenv("HOST"); oshost != "" {
a = oshost
} else if oshost := os.Getenv("ADDR"); oshost != "" {
a = oshost
} else if osport := os.Getenv("PORT"); osport != "" {
a = ":" + osport
}
if a == "" {
a = DefaultServerAddr
}
}
if portIdx := strings.IndexByte(a, ':'); portIdx == 0 {
// if contains only :port ,then the : is the first letter, so we dont have setted a hostname, lets set it
a = DefaultServerHostname + a
}
if portIdx := strings.IndexByte(a, ':'); portIdx < 0 {
// missing port part, add it
a = a + ":80"
}
return a
}
// Default values for base Server conf
const (
// DefaultServerHostname returns the default hostname which is 0.0.0.0
DefaultServerHostname = "0.0.0.0"
// DefaultServerPort returns the default port which is 8080
DefaultServerPort = 8080
// DefaultMaxRequestBodySize is 8MB
DefaultMaxRequestBodySize = 2 * fasthttp.DefaultMaxRequestBodySize
// Per-connection buffer size for requests' reading.
// This also limits the maximum header size.
//
// Increase this buffer if your clients send multi-KB RequestURIs
// and/or multi-KB headers (for example, BIG cookies).
//
// Default buffer size is 8MB
DefaultReadBufferSize = 8096
// Per-connection buffer size for responses' writing.
//
// Default buffer size is 8MB
DefaultWriteBufferSize = 8096
// DefaultServerName the response header of the 'Server' value when writes to the client
DefaultServerName = "iris"
)
var (
// DefaultServerAddr the default server addr which is: 0.0.0.0:8080
DefaultServerAddr = DefaultServerHostname + ":" + strconv.Itoa(DefaultServerPort)
)
// DefaultServerConfiguration returns the default configs for the server
func DefaultServerConfiguration() ServerConfiguration {
return ServerConfiguration{
ListeningAddr: DefaultServerAddr,
Name: DefaultServerName,
MaxRequestBodySize: DefaultMaxRequestBodySize,
ReadBufferSize: DefaultReadBufferSize,
WriteBufferSize: DefaultWriteBufferSize,
RedirectTo: "",
Virtual: false,
VListeningAddr: "",
VScheme: "",
}
}

102
configuration_test.go Normal file
View File

@ -0,0 +1,102 @@
package iris
import (
"reflect"
"testing"
)
// go test -v -run TestConfig*
func TestConfigStatic(t *testing.T) {
def := DefaultConfiguration()
api := New(def)
afterNew := *api.Config
if !reflect.DeepEqual(def, afterNew) {
t.Fatalf("Default configuration is not the same after NewFromConfig expected:\n %#v \ngot:\n %#v", def, afterNew)
}
afterNew.Charset = "changed"
if reflect.DeepEqual(def, afterNew) {
t.Fatalf("Configuration should be not equal, got: %#v", afterNew)
}
api = New(Configuration{IsDevelopment: true})
afterNew = *api.Config
if api.Config.IsDevelopment == false {
t.Fatalf("Passing a Configuration field as Option fails, expected IsDevelopment to be true but was false")
}
api = New() // empty , means defaults so
if !reflect.DeepEqual(def, *api.Config) {
t.Fatalf("Default configuration is not the same after NewFromConfig expected:\n %#v \ngot:\n %#v", def, *api.Config)
}
}
func TestConfigOptions(t *testing.T) {
charset := "MYCHARSET"
dev := true
api := New(OptionCharset(charset), OptionIsDevelopment(dev))
if got := api.Config.Charset; got != charset {
t.Fatalf("Expected configuration Charset to be: %s but got: %s", charset, got)
}
if got := api.Config.IsDevelopment; got != dev {
t.Fatalf("Expected configuration IsDevelopment to be: %#v but got: %#v", dev, got)
}
// now check if other default values are setted (should be setted automatically)
expected := DefaultConfiguration()
expected.Charset = charset
expected.IsDevelopment = dev
has := *api.Config
if !reflect.DeepEqual(has, expected) {
t.Fatalf("Default configuration is not the same after New expected:\n %#v \ngot:\n %#v", expected, has)
}
}
func TestConfigOptionsDeep(t *testing.T) {
cookiename := "MYSESSIONID"
charset := "MYCHARSET"
dev := true
profilePath := "/mypprof"
// first session, after charset,dev and profilepath, no canonical order.
api := New(OptionSessionsCookie(cookiename), OptionCharset(charset), OptionIsDevelopment(dev), OptionProfilePath(profilePath))
expected := DefaultConfiguration()
expected.Sessions.Cookie = cookiename
expected.Charset = charset
expected.IsDevelopment = dev
expected.ProfilePath = profilePath
has := *api.Config
if !reflect.DeepEqual(has, expected) {
t.Fatalf("DEEP configuration is not the same after New expected:\n %#v \ngot:\n %#v", expected, has)
}
}
// ServerConfiguration is independent so make a small test for that
func TestConfigServerOptions(t *testing.T) {
expected := DefaultServerConfiguration()
expected.ListeningAddr = "mydomain.com:80"
expected.RedirectTo = "https://mydomain.com:443"
expected.Virtual = true
c := ServerConfiguration{ListeningAddr: expected.ListeningAddr, RedirectTo: expected.RedirectTo, Virtual: expected.Virtual}
// static config test
s := newServer(c)
if !reflect.DeepEqual(s.Config, expected) {
t.Fatalf("Static Server Configuration not equal after newServer, expected:\n%#v \nwhile got:\n%#v", expected, s.Config)
}
}

View File

@ -10,6 +10,13 @@ import (
"encoding/json"
"encoding/xml"
"fmt"
"github.com/iris-contrib/formBinder"
"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"
"io"
"net"
"os"
@ -18,18 +25,7 @@ import (
"runtime"
"strconv"
"strings"
"sync"
"time"
"github.com/iris-contrib/formBinder"
"github.com/kataras/go-errors"
"github.com/kataras/go-fs"
"github.com/kataras/go-sessions"
"github.com/kataras/iris/config"
"github.com/kataras/iris/context"
"github.com/kataras/iris/utils"
"github.com/klauspost/compress/gzip"
"github.com/valyala/fasthttp"
)
const (
@ -80,9 +76,6 @@ const (
cookieHeaderIDLen = len(cookieHeaderID)
)
// this pool is used everywhere needed in the iris for example inside party-> Static
var gzipWriterPool = sync.Pool{New: func() interface{} { return &gzip.Writer{} }}
// errors
var (
@ -541,14 +534,29 @@ func (ctx *Context) Gzip(b []byte, status int) {
}
}
// RenderTemplateSource serves a template source(raw string contents) from the first template engines which supports raw parsing returns its result as string
func (ctx *Context) RenderTemplateSource(status int, src string, binding interface{}, options ...map[string]interface{}) error {
err := ctx.framework.templates.renderSource(ctx, src, binding, options...)
if err == nil {
ctx.SetStatusCode(status)
}
return err
}
// RenderWithStatus builds up the response from the specified template or a response engine.
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or response engine
func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, options ...map[string]interface{}) error {
ctx.SetStatusCode(status)
func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, options ...map[string]interface{}) (err error) {
if strings.IndexByte(name, '.') > -1 { //we have template
return ctx.framework.templates.render(ctx, name, binding, options...)
err = ctx.framework.templates.render(ctx, name, binding, options...)
}
return ctx.framework.responses.getBy(name).render(ctx, binding, options...)
err = ctx.framework.responses.getBy(name).render(ctx, binding, options...)
if err == nil {
ctx.SetStatusCode(status)
}
return
}
// Render same as .RenderWithStatus but with status to iris.StatusOK (200) if no previous status exists
@ -634,7 +642,7 @@ func (ctx *Context) Markdown(status int, markdown string) {
// You can define your own "Content-Type" header also, after this function call
// Doesn't implements resuming (by range), use ctx.SendFile instead
func (ctx *Context) ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error {
if t, err := time.Parse(config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && modtime.Before(t.Add(1*time.Second)) {
if t, err := time.Parse(ctx.framework.Config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && modtime.Before(t.Add(1*time.Second)) {
ctx.RequestCtx.Response.Header.Del(contentType)
ctx.RequestCtx.Response.Header.Del(contentLength)
ctx.RequestCtx.SetStatusCode(StatusNotModified)
@ -642,20 +650,18 @@ func (ctx *Context) ServeContent(content io.ReadSeeker, filename string, modtime
}
ctx.RequestCtx.Response.Header.Set(contentType, fs.TypeByExtension(filename))
ctx.RequestCtx.Response.Header.Set(lastModified, modtime.UTC().Format(config.TimeFormat))
ctx.RequestCtx.Response.Header.Set(lastModified, modtime.UTC().Format(ctx.framework.Config.TimeFormat))
ctx.RequestCtx.SetStatusCode(StatusOK)
var out io.Writer
if gzipCompression && ctx.clientAllowsGzip() {
ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
ctx.SetHeader(contentEncodingHeader, "gzip")
gzipWriter := gzipWriterPool.Get().(*gzip.Writer)
gzipWriter.Reset(ctx.RequestCtx.Response.BodyWriter())
defer gzipWriter.Close()
defer gzipWriterPool.Put(gzipWriter)
gzipWriter := fs.AcquireGzipWriter(ctx.RequestCtx.Response.BodyWriter())
defer fs.ReleaseGzipWriter(gzipWriter)
out = gzipWriter
} else {
out = ctx.RequestCtx.Response.BodyWriter()
}
_, err := io.Copy(out, content)
return errServeContent.With(err)

26
http.go
View File

@ -17,7 +17,6 @@ import (
"github.com/iris-contrib/letsencrypt"
"github.com/kataras/go-errors"
"github.com/kataras/iris/config"
"github.com/kataras/iris/utils"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttpadaptor"
@ -244,7 +243,7 @@ type (
Server struct {
*fasthttp.Server
listener net.Listener
Config config.Server
Config ServerConfiguration
tls bool
mu sync.Mutex
}
@ -256,11 +255,13 @@ type (
)
// newServer returns a pointer to a Server object, and set it's options if any, nothing more
func newServer(cfg config.Server) *Server {
if cfg.Name == "" {
cfg.Name = config.DefaultServerName
func newServer(setters ...OptionServerSettter) *Server {
c := DefaultServerConfiguration()
for _, setter := range setters {
setter.Set(&c)
}
s := &Server{Server: &fasthttp.Server{Name: cfg.Name}, Config: cfg}
s := &Server{Server: &fasthttp.Server{Name: c.Name}, Config: c}
return s
}
@ -436,6 +437,7 @@ func (s *Server) Open(h fasthttp.RequestHandler) error {
s.Server.WriteBufferSize = s.Config.WriteBufferSize
s.Server.ReadTimeout = s.Config.ReadTimeout
s.Server.WriteTimeout = s.Config.WriteTimeout
if s.Config.RedirectTo != "" {
// override the handler and redirect all requests to this addr
s.Server.Handler = func(reqCtx *fasthttp.RequestCtx) {
@ -454,7 +456,7 @@ func (s *Server) Open(h fasthttp.RequestHandler) error {
return s.listenUNIX()
}
s.Config.ListeningAddr = config.ServerParseAddr(s.Config.ListeningAddr)
s.Config.ListeningAddr = ServerParseAddr(s.Config.ListeningAddr)
if s.Config.Virtual {
return nil
@ -482,8 +484,8 @@ func (s *Server) Close() (err error) {
// Add adds a server to the list by its config
// returns the new server
func (s *ServerList) Add(cfg config.Server) *Server {
srv := newServer(cfg)
func (s *ServerList) Add(setters ...OptionServerSettter) *Server {
srv := newServer(setters...)
s.servers = append(s.servers, srv)
return srv
}
@ -1356,9 +1358,9 @@ func newServeMux(logger *log.Logger) *serveMux {
mux := &serveMux{
lookups: make([]*route, 0),
errorHandlers: make(map[int]Handler, 0),
hostname: config.DefaultServerHostname, // these are changing when the server is up
escapePath: !config.DefaultDisablePathEscape,
correctPath: !config.DefaultDisablePathCorrection,
hostname: DefaultServerHostname, // these are changing when the server is up
escapePath: !DefaultDisablePathEscape,
correctPath: !DefaultDisablePathCorrection,
logger: logger,
}

View File

@ -14,7 +14,6 @@ import (
"time"
"github.com/gavv/httpexpect"
"github.com/kataras/iris/config"
)
const (
@ -72,14 +71,14 @@ func TestServerHost(t *testing.T) {
var server1, server2, server3 Server
var expectedHost1 = "mydomain.com:1993"
var expectedHost2 = "mydomain.com:80"
var expectedHost3 = config.DefaultServerHostname + ":9090"
var expectedHost3 = DefaultServerHostname + ":9090"
server1.Config.ListeningAddr = expectedHost1
server2.Config.ListeningAddr = "mydomain.com"
server3.Config.ListeningAddr = ":9090"
server1.Config.ListeningAddr = config.ServerParseAddr(server1.Config.ListeningAddr)
server2.Config.ListeningAddr = config.ServerParseAddr(server2.Config.ListeningAddr)
server3.Config.ListeningAddr = config.ServerParseAddr(server3.Config.ListeningAddr)
server1.Config.ListeningAddr = ServerParseAddr(server1.Config.ListeningAddr)
server2.Config.ListeningAddr = ServerParseAddr(server2.Config.ListeningAddr)
server3.Config.ListeningAddr = ServerParseAddr(server3.Config.ListeningAddr)
if server1.Host() != expectedHost1 {
t.Fatalf("Expecting server 1's host to be %s but we got %s", expectedHost1, server1.Host())
@ -96,7 +95,7 @@ func TestServerHostname(t *testing.T) {
var server Server
var expectedHostname = "mydomain.com"
server.Config.ListeningAddr = expectedHostname + ":1993"
server.Config.ListeningAddr = config.ServerParseAddr(server.Config.ListeningAddr)
server.Config.ListeningAddr = ServerParseAddr(server.Config.ListeningAddr)
if server.Hostname() != expectedHostname {
t.Fatalf("Expecting server's hostname to be %s but we got %s", expectedHostname, server.Hostname())
}
@ -126,8 +125,8 @@ func TestServerPort(t *testing.T) {
expectedPort2 := 80
server1.Config.ListeningAddr = "mydomain.com:8080"
server2.Config.ListeningAddr = "mydomain.com"
server1.Config.ListeningAddr = config.ServerParseAddr(server1.Config.ListeningAddr)
server2.Config.ListeningAddr = config.ServerParseAddr(server2.Config.ListeningAddr)
server1.Config.ListeningAddr = ServerParseAddr(server1.Config.ListeningAddr)
server2.Config.ListeningAddr = ServerParseAddr(server2.Config.ListeningAddr)
if server1.Port() != expectedPort1 {
t.Fatalf("Expecting server 1's port to be %d but we got %d", expectedPort1, server1.Port())
@ -172,9 +171,9 @@ func TestMultiRunningServers_v1(t *testing.T) {
})
// start the secondary server
AddServer(config.Server{ListeningAddr: "mydomain.com:80", RedirectTo: "https://" + host, Virtual: true})
AddServer(ServerConfiguration{ListeningAddr: "mydomain.com:80", RedirectTo: "https://" + host, Virtual: true})
// start the main server
go ListenTo(config.Server{ListeningAddr: host, CertFile: certFile.Name(), KeyFile: keyFile.Name(), Virtual: true})
go ListenTo(ServerConfiguration{ListeningAddr: host, CertFile: certFile.Name(), KeyFile: keyFile.Name(), Virtual: true})
// prepare test framework
if ok := <-Available; !ok {
t.Fatal("Unexpected error: server cannot start, please report this as bug!!")
@ -224,9 +223,9 @@ func TestMultiRunningServers_v2(t *testing.T) {
})
// add a secondary server
Servers.Add(config.Server{ListeningAddr: domain + ":80", RedirectTo: "https://" + host, Virtual: true})
Servers.Add(ServerConfiguration{ListeningAddr: domain + ":80", RedirectTo: "https://" + host, Virtual: true})
// add our primary/main server
Servers.Add(config.Server{ListeningAddr: host, CertFile: certFile.Name(), KeyFile: keyFile.Name(), Virtual: true})
Servers.Add(ServerConfiguration{ListeningAddr: host, CertFile: certFile.Name(), KeyFile: keyFile.Name(), Virtual: true})
go Go()

217
iris.go
View File

@ -76,7 +76,6 @@ import (
"github.com/kataras/go-sessions"
"github.com/kataras/go-template"
"github.com/kataras/go-template/html"
"github.com/kataras/iris/config"
"github.com/kataras/iris/context"
"github.com/kataras/iris/utils"
"github.com/valyala/fasthttp"
@ -84,7 +83,7 @@ import (
const (
// Version of the iris
Version = "4.1.7"
Version = "4.2.0"
banner = ` _____ _
|_ _| (_)
@ -97,7 +96,7 @@ const (
// Default entry, use it with iris.$anyPublicFunc
var (
Default *Framework
Config *config.Iris
Config *Configuration
Logger *log.Logger // if you want colors in your console then you should use this https://github.com/iris-contrib/logger instead.
Plugins PluginContainer
Websocket *WebsocketServer
@ -143,8 +142,9 @@ type (
FrameworkAPI interface {
MuxAPI
Must(error)
AddServer(config.Server) *Server
ListenTo(config.Server) error
Set(...OptionSetter)
AddServer(...OptionServerSettter) *Server
ListenTo(...OptionServerSettter) error
Listen(string)
ListenTLS(string, string, string)
ListenUNIX(string, os.FileMode)
@ -161,8 +161,9 @@ type (
Path(string, ...interface{}) string
URL(string, ...interface{}) string
TemplateString(string, interface{}, ...map[string]interface{}) string
TemplateSourceString(string, interface{}) string
ResponseString(string, interface{}, ...map[string]interface{}) string
Tester(t *testing.T) *httpexpect.Expect
Tester(*testing.T) *httpexpect.Expect
}
// Framework is our God |\| Google.Search('Greek mythology Iris')
@ -171,7 +172,7 @@ type (
Framework struct {
*muxAPI
contextPool sync.Pool
Config *config.Iris
Config *Configuration
sessions sessions.Sessions
responses *responseEngines
templates *templateEngines
@ -191,55 +192,76 @@ type (
var _ FrameworkAPI = &Framework{}
// New creates and returns a new Iris station aka Framework.
// New creates and returns a new Iris instance.
//
// Receives an optional config.Iris as parameter
// If empty then config.Default() is used instead
func New(cfg ...config.Iris) *Framework {
c := config.Default().Merge(cfg)
// Receives (optional) multi options, use iris.Option and your editor should show you the available options to set
// all options are inside ./configuration.go
// example 1: iris.New(iris.OptionIsDevelopment(true), iris.OptionCharset("UTF-8"), irisOptionSessionsCookie("mycookieid"),iris.OptionWebsocketEndpoint("my_endpoint"))
// example 2: iris.New(iris.Configuration{IsDevelopment:true, Charset: "UTF-8", Sessions: iris.SessionsConfiguration{Cookie:"mycookieid"}, Websocket: iris.WebsocketConfiguration{Endpoint:"/my_endpoint"}})
// both ways are totally valid and equal
func New(setters ...OptionSetter) *Framework {
// we always use 's' no 'f' because 's' is easier for me to remember because of 'station'
// some things never change :)
s := &Framework{
Config: &c,
// set the Logger
Logger: log.New(c.LoggerOut, c.LoggerPreffix, log.LstdFlags),
responses: &responseEngines{},
Available: make(chan bool),
SSH: &SSHServer{},
// set the sessions, configuration willbe updated on the initialization also, in order to give the user the opportunity to change its config at runtime.
sessions: sessions.New(sessions.Config(c.Sessions)),
}
s := &Framework{}
s.Set(setters...)
// logger, plugins & ssh
{
s.contextPool.New = func() interface{} {
return &Context{framework: s}
}
///NOTE: set all with s.Config pointer
// set the plugin container
// set the Logger, which it's configuration should be declared before .Listen because the servemux and plugins needs that
s.Logger = log.New(s.Config.LoggerOut, s.Config.LoggerPreffix, log.LstdFlags)
s.Plugins = newPluginContainer(s.Logger)
s.SSH = NewSSHServer()
}
// rendering
{
s.responses = newResponseEngines()
// set the templates
s.templates = newTemplateEngines(map[string]interface{}{
"url": s.URL,
"urlpath": s.Path,
})
// set the websocket server
s.Websocket = NewWebsocketServer(s.Config.Websocket)
}
// websocket
{
s.Websocket = NewWebsocketServer() // in order to be able to call $instance.Websocket.OnConnection
}
// routing & http server
{
// set the servemux, which will provide us the public API also, with its context pool
mux := newServeMux(s.Logger)
mux.onLookup = s.Plugins.DoPreLookup
// set the public router API (and party)
s.muxAPI = &muxAPI{mux: mux, relativePath: "/"}
s.contextPool.New = func() interface{} {
return &Context{framework: s}
}
s.Servers = &ServerList{mux: mux, servers: make([]*Server, 0)}
s.Available = make(chan bool)
}
return s
}
func (s *Framework) initialize() {
// Set sets an option aka configuration field to the default iris instance
func Set(setters ...OptionSetter) {
Default.Set(setters...)
}
s.sessions.UpdateConfig(sessions.Config(s.Config.Sessions))
// Set sets an option aka configuration field to this iris instance
func (s *Framework) Set(setters ...OptionSetter) {
if s.Config == nil {
defaultConfiguration := DefaultConfiguration()
s.Config = &defaultConfiguration
}
for _, setter := range setters {
setter.Set(s.Config)
}
}
func (s *Framework) initialize() {
// prepare the response engines, if no response engines setted for the default content-types
// then add them
@ -276,8 +298,13 @@ func (s *Framework) initialize() {
s.Logger.Panic(err) // panic on templates loading before listening if we have an error.
}
}
// listen to websocket connections
RegisterWebsocketServer(s, s.Websocket, s.Logger)
// set the sessions
s.sessions = sessions.New(sessions.Config(s.Config.Sessions))
if s.Config.Websocket.Endpoint != "" {
// register the websocket server and listen to websocket connections when/if $instance.Websocket.OnConnection called by the dev
s.Websocket.RegisterTo(s, s.Config.Websocket)
}
// prepare the mux & the server
s.mux.setCorrectPath(!s.Config.DisablePathCorrection)
@ -328,7 +355,7 @@ func (s *Framework) Go() error {
hosts[i] = srv.Host()
}
bannerMessage := fmt.Sprintf("%s: Running at %s", time.Now().Format(config.TimeFormat), strings.Join(hosts, ", "))
bannerMessage := fmt.Sprintf("%s: Running at %s", time.Now().Format(s.Config.TimeFormat), strings.Join(hosts, ", "))
// we don't print it via Logger because:
// 1. The banner is only 'useful' when the developer logs to terminal and no file
// 2. Prefix & LstdFlags options of the default s.Logger
@ -358,68 +385,60 @@ func (s *Framework) Must(err error) {
}
}
// AddServer same as .Servers.Add(config.Server)
// AddServer same as .Servers.Add(ServerConfiguration)
//
// AddServer starts a server which listens to this station
// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https)
//
// this is useful mostly when you want to have two or more listening ports ( two or more servers ) for the same station
//
// receives one parameter which is the config.Server for the new server
// receives one parameter which is the ServerConfiguration for the new server
// returns the new standalone server( you can close this server by the returning reference)
//
// If you need only one server you can use the blocking-funcs: .Listen/ListenTLS/ListenUNIX/ListenTo
//
// this is a NOT A BLOCKING version, the main .Listen/ListenTLS/ListenUNIX/ListenTo should be always executed LAST, so this function goes before the main .Listen/ListenTLS/ListenUNIX/ListenTo
func AddServer(cfg config.Server) *Server {
return Default.AddServer(cfg)
func AddServer(setters ...OptionServerSettter) *Server {
return Default.AddServer(setters...)
}
// AddServer same as .Servers.Add(config.Server)
// AddServer same as .Servers.Add(ServerConfiguration)
//
// AddServer starts a server which listens to this station
// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the last registered server's scheme (http/https)
//
// this is useful mostly when you want to have two or more listening ports ( two or more servers ) for the same station
//
// receives one parameter which is the config.Server for the new server
// receives one parameter which is the ServerConfiguration for the new server
// returns the new standalone server( you can close this server by the returning reference)
//
// If you need only one server you can use the blocking-funcs: .Listen/ListenTLS/ListenUNIX/ListenTo
//
// this is a NOT A BLOCKING version, the main .Listen/ListenTLS/ListenUNIX/ListenTo should be always executed LAST, so this function goes before the main .Listen/ListenTLS/ListenUNIX/ListenTo
func (s *Framework) AddServer(cfg config.Server) *Server {
return s.Servers.Add(cfg)
func (s *Framework) AddServer(setters ...OptionServerSettter) *Server {
return s.Servers.Add(setters...)
}
// ListenTo listens to a server but accepts the full server's configuration
// returns an error, you're responsible to handle that
// or use the iris.Must(iris.ListenTo(config.Server{}))
// ex: ris.ListenTo(iris.ServerConfiguration{ListeningAddr:":8080"})
// ex2: err := iris.ListenTo(iris.OptionServerListeningAddr(":8080"))
// or use the iris.Must(iris.ListenTo(iris.ServerConfiguration{ListeningAddr:":8080"}))
//
// it's a blocking func
func ListenTo(cfg config.Server) error {
return Default.ListenTo(cfg)
func ListenTo(setters ...OptionServerSettter) error {
return Default.ListenTo(setters...)
}
// ListenTo listens to a server but acceots the full server's configuration
// returns an error, you're responsible to handle that
// or use the iris.Must(iris.ListenTo(config.Server{}))
// ex: ris.ListenTo(iris.ServerConfiguration{ListeningAddr:":8080"})
// ex2: err := iris.ListenTo(iris.OptionServerListeningAddr(":8080"))
// or use the iris.Must(iris.ListenTo(iris.ServerConfiguration{ListeningAddr:":8080"}))
//
// it's a blocking func
func (s *Framework) ListenTo(cfg config.Server) (err error) {
if cfg.ReadBufferSize == 0 {
cfg.ReadBufferSize = config.DefaultReadBufferSize
}
if cfg.WriteBufferSize == 0 {
cfg.WriteBufferSize = config.DefaultWriteBufferSize
}
if cfg.MaxRequestBodySize == 0 {
cfg.MaxRequestBodySize = config.DefaultMaxRequestBodySize
}
if cfg.ListeningAddr == "" {
cfg.ListeningAddr = config.DefaultServerAddr
}
s.Servers.Add(cfg)
func (s *Framework) ListenTo(setters ...OptionServerSettter) (err error) {
s.Servers.Add(setters...)
return s.Go()
}
@ -428,7 +447,6 @@ func (s *Framework) ListenTo(cfg config.Server) (err error) {
// host:port
//
// It panics on error if you need a func to return an error, use the ListenTo
// ex: err := iris.ListenTo(config.Server{ListeningAddr:":8080"})
func Listen(addr string) {
Default.Listen(addr)
}
@ -438,9 +456,8 @@ func Listen(addr string) {
// host:port
//
// It panics on error if you need a func to return an error, use the ListenTo
// ex: err := iris.ListenTo(config.Server{ListeningAddr:":8080"})
func (s *Framework) Listen(addr string) {
s.Must(s.ListenTo(config.Server{ListeningAddr: addr}))
s.Must(s.ListenTo(ServerConfiguration{ListeningAddr: addr}))
}
// ListenTLS Starts a https server with certificates,
@ -450,7 +467,7 @@ func (s *Framework) Listen(addr string) {
// host:port
//
// It panics on error if you need a func to return an error, use the ListenTo
// ex: err := iris.ListenTo(":8080","yourfile.cert","yourfile.key")
// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key")
func ListenTLS(addr string, certFile string, keyFile string) {
Default.ListenTLS(addr, certFile, keyFile)
}
@ -461,7 +478,7 @@ func ListenTLS(addr string, certFile string, keyFile string) {
//
// Notes:
// if you don't want the last feature you should use this method:
// iris.ListenTo(config.Server{ListeningAddr: "mydomain.com:443", AutoTLS: true})
// iris.ListenTo(iris.ServerConfiguration{ListeningAddr: "mydomain.com:443", AutoTLS: true})
// it's a blocking function
// Limit : https://github.com/iris-contrib/letsencrypt/blob/master/lets.go#L142
//
@ -477,12 +494,12 @@ func ListenTLSAuto(addr string) {
// host:port
//
// It panics on error if you need a func to return an error, use the ListenTo
// ex: err := iris.ListenTo(":8080","yourfile.cert","yourfile.key")
// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key")
func (s *Framework) ListenTLS(addr string, certFile, keyFile string) {
if certFile == "" || keyFile == "" {
s.Logger.Panic("You should provide certFile and keyFile for TLS/SSL")
}
s.Must(s.ListenTo(config.Server{ListeningAddr: addr, CertFile: certFile, KeyFile: keyFile}))
s.Must(s.ListenTo(ServerConfiguration{ListeningAddr: addr, CertFile: certFile, KeyFile: keyFile}))
}
// ListenTLSAuto starts a server listening at the specific nat address
@ -491,7 +508,7 @@ func (s *Framework) ListenTLS(addr string, certFile, keyFile string) {
//
// Notes:
// if you don't want the last feature you should use this method:
// iris.ListenTo(config.Server{ListeningAddr: "mydomain.com:443", AutoTLS: true})
// iris.ListenTo(iris.ServerConfiguration{ListeningAddr: "mydomain.com:443", AutoTLS: true})
// it's a blocking function
// Limit : https://github.com/iris-contrib/letsencrypt/blob/master/lets.go#L142
//
@ -500,18 +517,18 @@ func (s *Framework) ListenTLSAuto(addr string) {
if portIdx := strings.IndexByte(addr, ':'); portIdx == -1 {
addr += ":443"
}
addr = config.ServerParseAddr(addr)
addr = ServerParseAddr(addr)
// start a secondary server (HTTP) on port 80, this is a non-blocking func
// redirects all http to the main server which is tls/ssl on port :443
s.AddServer(config.Server{ListeningAddr: ":80", RedirectTo: "https://" + addr})
s.Must(s.ListenTo(config.Server{ListeningAddr: addr, AutoTLS: true}))
s.AddServer(ServerConfiguration{ListeningAddr: ":80", RedirectTo: "https://" + addr})
s.Must(s.ListenTo(ServerConfiguration{ListeningAddr: addr, AutoTLS: true}))
}
// ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix
//
// It panics on error if you need a func to return an error, use the ListenTo
// ex: err := iris.ListenTo(":8080", Mode: os.FileMode)
// ex: iris.ListenUNIX(":8080", Mode: os.FileMode)
func ListenUNIX(addr string, mode os.FileMode) {
Default.ListenUNIX(addr, mode)
}
@ -519,9 +536,9 @@ func ListenUNIX(addr string, mode os.FileMode) {
// ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix
//
// It panics on error if you need a func to return an error, use the ListenTo
// ex: err := iris.ListenTo(":8080", Mode: os.FileMode)
// ex: ris.ListenUNIX(":8080", Mode: os.FileMode)
func (s *Framework) ListenUNIX(addr string, mode os.FileMode) {
s.Must(ListenTo(config.Server{ListeningAddr: addr, Mode: mode}))
s.Must(ListenTo(ServerConfiguration{ListeningAddr: addr, Mode: mode}))
}
// ListenVirtual is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
@ -536,7 +553,7 @@ func ListenVirtual(optionalAddr ...string) *Server {
// it is not blocking the app
func (s *Framework) ListenVirtual(optionalAddr ...string) *Server {
s.Config.DisableBanner = true
cfg := config.DefaultServer()
cfg := DefaultServerConfiguration()
if len(optionalAddr) > 0 && optionalAddr[0] != "" {
cfg.ListeningAddr = optionalAddr[0]
@ -901,6 +918,27 @@ func (s *Framework) TemplateString(templateFile string, pageContext interface{},
return res
}
// TemplateSourceString executes a template source(raw string contents) from the first template engines which supports raw parsing returns its result as string,
// useful when you want it for sending rich e-mails
// returns empty string on error
func TemplateSourceString(src string, pageContext interface{}) string {
return Default.TemplateSourceString(src, pageContext)
}
// TemplateSourceString executes a template source(raw string contents) from the first template engines which supports raw parsing returns its result as string,
// useful when you want it for sending rich e-mails
// returns empty string on error
func (s *Framework) TemplateSourceString(src string, pageContext interface{}) string {
if s.Config.DisableTemplateEngines {
return ""
}
res, err := s.templates.ExecuteRawString(src, pageContext)
if err != nil {
res = ""
}
return res
}
// ResponseString returns the string of a response engine,
// does not render it to the client
// returns empty string on error
@ -1527,8 +1565,8 @@ func (api *muxAPI) StaticHandler(systemPath string, stripSlashes int, compress b
// Enable transparent compression to save network traffic.
Compress: compress,
CacheDuration: config.StaticCacheDuration,
CompressedFileSuffix: config.CompressedFileSuffix,
CacheDuration: StaticCacheDuration,
CompressedFileSuffix: CompressedFileSuffix,
}
if stripSlashes > 0 {
@ -1713,9 +1751,13 @@ func StaticContent(reqPath string, contentType string, content []byte) RouteName
// a good example of this is how the websocket server uses that to auto-register the /iris-ws.js
func (api *muxAPI) StaticContent(reqPath string, cType string, content []byte) RouteNameFunc { // func(string) because we use that on websockets
modtime := time.Now()
modtimeStr := modtime.UTC().Format(config.TimeFormat)
modtimeStr := ""
h := func(ctx *Context) {
if t, err := time.Parse(config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && modtime.Before(t.Add(config.StaticCacheDuration)) {
if modtimeStr == "" {
modtimeStr = modtime.UTC().Format(ctx.framework.Config.TimeFormat)
}
if t, err := time.Parse(ctx.framework.Config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && modtime.Before(t.Add(StaticCacheDuration)) {
ctx.Response.Header.Del(contentType)
ctx.Response.Header.Del(contentLength)
ctx.SetStatusCode(StatusNotModified)
@ -1772,16 +1814,19 @@ func (api *muxAPI) Favicon(favPath string, requestPath ...string) RouteNameFunc
favPath = fav
fi, _ = f.Stat()
}
modtime := fi.ModTime().UTC().Format(config.TimeFormat)
cType := fs.TypeByExtension(favPath)
// copy the bytes here in order to cache and not read the ico on each request.
cacheFav := make([]byte, fi.Size())
if _, err = f.Read(cacheFav); err != nil {
panic(errDirectoryFileNotFound.Format(favPath, "Couldn't read the data bytes for Favicon: "+err.Error()))
}
modtime := ""
h := func(ctx *Context) {
if t, err := time.Parse(config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && fi.ModTime().Before(t.Add(config.StaticCacheDuration)) {
if modtime == "" {
modtime = fi.ModTime().UTC().Format(ctx.framework.Config.TimeFormat)
}
if t, err := time.Parse(ctx.framework.Config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && fi.ModTime().Before(t.Add(StaticCacheDuration)) {
ctx.Response.Header.Del(contentType)
ctx.Response.Header.Del(contentLength)
ctx.SetStatusCode(StatusNotModified)

View File

@ -184,6 +184,10 @@ type responseEngines struct {
engines []*responseEngineMap
}
func newResponseEngines() *responseEngines {
return &responseEngines{}
}
// add accepts a simple response engine with its content type or key, key should not contains a dot('.').
// if key is a content type then it's the content type, but if it not, set the content type from the returned function,
// if it not called/changed then the default content type text/plain will be used.

5
ssh.go
View File

@ -428,6 +428,11 @@ type SSHServer struct {
Logger *log.Logger // log.New(...)/ $qinstance.Logger, fill it when you want to receive debug and info/warnings messages
}
// NewSSHServer returns a new empty SSHServer
func NewSSHServer() *SSHServer {
return &SSHServer{}
}
// Enabled returns true if SSH can be started, if Host != ""
func (s *SSHServer) Enabled() bool {
if s == nil {

View File

@ -73,3 +73,31 @@ func (t *templateEngines) render(ctx *Context, filename string, binding interfac
err = t.ExecuteWriter(out, filename, binding, options...)
return err
}
// renderSource executes a template source raw contents (string) and write its result to the context's body
// note that gzip option is an iris dynamic option which exists for all template engines
// the gzip and charset options are built'n with iris
func (t *templateEngines) renderSource(ctx *Context, src string, binding interface{}, options ...map[string]interface{}) (err error) {
// we do all these because we don't want to initialize a new map for each execution...
gzipEnabled := ctx.framework.Config.Gzip
charset := ctx.framework.Config.Charset
if len(options) > 0 {
gzipEnabled = template.GetGzipOption(gzipEnabled, options[0])
charset = template.GetCharsetOption(charset, options[0])
}
ctx.SetContentType(contentHTML + "; charset=" + charset)
var out io.Writer
if gzipEnabled && ctx.clientAllowsGzip() {
ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
ctx.SetHeader(contentEncodingHeader, "gzip")
gzipWriter := fs.AcquireGzipWriter(ctx.Response.BodyWriter())
defer fs.ReleaseGzipWriter(gzipWriter)
out = gzipWriter
} else {
out = ctx.Response.BodyWriter()
}
return t.ExecuteRaw(src, out, binding)
}

View File

@ -1,86 +1,10 @@
package iris
import (
"log"
irisWebsocket "github.com/iris-contrib/websocket"
"github.com/kataras/go-websocket"
"github.com/kataras/iris/config"
)
// ---------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------
// --------------------------------Websocket implementation-------------------------------------------------
// Global functions in order to be able to use unlimitted number of websocket servers on each iris station--
// ---------------------------------------------------------------------------------------------------------
// Note I keep this code only to no change the front-end API, we could only use the go-websocket and set our custom upgrader
// NewWebsocketServer creates a websocket server and returns it
func NewWebsocketServer(c *config.Websocket) *WebsocketServer {
wsConfig := websocket.Config{
WriteTimeout: c.WriteTimeout,
PongTimeout: c.PongTimeout,
PingPeriod: c.PingPeriod,
MaxMessageSize: c.MaxMessageSize,
BinaryMessages: c.BinaryMessages,
ReadBufferSize: c.ReadBufferSize,
WriteBufferSize: c.WriteBufferSize,
}
wsServer := websocket.New(wsConfig)
upgrader := irisWebsocket.Custom(wsServer.HandleConnection, c.ReadBufferSize, c.WriteBufferSize, false)
srv := &WebsocketServer{Server: wsServer, Config: c, upgrader: upgrader}
return srv
}
// RegisterWebsocketServer registers the handlers for the websocket server
// it's a bridge between station and websocket server
func RegisterWebsocketServer(station FrameworkAPI, server *WebsocketServer, logger *log.Logger) {
c := server.Config
if c.Endpoint == "" {
return
}
websocketHandler := func(ctx *Context) {
if err := server.Upgrade(ctx); err != nil {
if ctx.framework.Config.IsDevelopment {
logger.Printf("Websocket error while trying to Upgrade the connection. Trace: %s", err.Error())
}
ctx.EmitError(StatusBadRequest)
}
}
if c.Headers != nil && len(c.Headers) > 0 { // only for performance matter just re-create the websocketHandler if we have headers to set
websocketHandler = func(ctx *Context) {
for k, v := range c.Headers {
ctx.SetHeader(k, v)
}
if err := server.Upgrade(ctx); err != nil {
if ctx.framework.Config.IsDevelopment {
logger.Printf("Websocket error while trying to Upgrade the connection. Trace: %s", err.Error())
}
ctx.EmitError(StatusBadRequest)
}
}
}
clientSideLookupName := "iris-websocket-client-side"
station.Get(c.Endpoint, websocketHandler)
// check if client side already exists
if station.Lookup(clientSideLookupName) == nil {
// serve the client side on domain:port/iris-ws.js
station.StaticContent("/iris-ws.js", contentJavascript, websocket.ClientSource)(clientSideLookupName)
}
// run the ws server
server.Serve()
}
// conversionals
const (
// All is the string which the Emmiter use to send a message to all
@ -91,16 +15,38 @@ const (
Broadcast = websocket.Broadcast
)
// newUnderlineWsServer returns a new go-websocket.Server from configuration, used internaly by Iris.
func newUnderlineWsServer(c WebsocketConfiguration) websocket.Server {
wsConfig := websocket.Config{
WriteTimeout: c.WriteTimeout,
PongTimeout: c.PongTimeout,
PingPeriod: c.PingPeriod,
MaxMessageSize: c.MaxMessageSize,
BinaryMessages: c.BinaryMessages,
ReadBufferSize: c.ReadBufferSize,
WriteBufferSize: c.WriteBufferSize,
}
return websocket.New(wsConfig)
}
// Note I keep this code only to no change the front-end API, we could only use the go-websocket and set our custom upgrader
type (
// WebsocketServer is the iris websocket server, expose the websocket.Server
// the below code is a wrapper and bridge between iris-contrib/websocket and kataras/go-websocket
WebsocketServer struct {
websocket.Server
Config *config.Websocket
config WebsocketConfiguration
upgrader irisWebsocket.Upgrader
}
)
// NewWebsocketServer returns an empty WebsocketServer, nothing special here.
func NewWebsocketServer() *WebsocketServer {
return &WebsocketServer{}
}
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
//
// The responseHeader is included in the response to the client's upgrade
@ -113,6 +59,30 @@ func (s *WebsocketServer) Upgrade(ctx *Context) error {
return s.upgrader.Upgrade(ctx)
}
// Handler is the iris Handler to upgrade the request
// used inside RegisterRoutes
func (s *WebsocketServer) Handler(ctx *Context) {
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)
}
}
// RegisterTo creates the client side source route and the route path Endpoint with the correct Handler
// receives the websocket configuration and the iris station
func (s *WebsocketServer) RegisterTo(station *Framework, c WebsocketConfiguration) {
s.config = c // save the configuration, we will need that on the .OnConnection
clientSideLookupName := "iris-websocket-client-side"
station.Get(s.config.Endpoint, s.Handler)
// check if client side already exists
if station.Lookup(clientSideLookupName) == nil {
// serve the client side on domain:port/iris-ws.js
station.StaticContent("/iris-ws.js", contentJavascript, websocket.ClientSource)(clientSideLookupName)
}
}
// WebsocketConnection is the front-end API that you will use to communicate with the client side
type WebsocketConnection interface {
websocket.Connection
@ -120,6 +90,17 @@ type WebsocketConnection interface {
// OnConnection this is the main event you, as developer, will work with each of the websocket connections
func (s *WebsocketServer) OnConnection(connectionListener func(WebsocketConnection)) {
// let's initialize here the ws server, the user/dev is free to change its config before this step.
if s.Server == nil {
s.Server = newUnderlineWsServer(s.config)
}
if s.upgrader.Receiver == nil {
s.upgrader = irisWebsocket.Custom(s.Server.HandleConnection, s.config.ReadBufferSize, s.config.WriteBufferSize, false)
// run the ws server
s.Server.Serve()
}
s.Server.OnConnection(func(c websocket.Connection) {
connectionListener(c)
})