diff --git a/HISTORY.md b/HISTORY.md
index c1d5d32a..a4b0f2fb 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -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
diff --git a/README.md b/README.md
index 9257263d..6d33e3e1 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
-
+
@@ -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
diff --git a/config/config.go b/config/config.go
deleted file mode 100644
index 78168368..00000000
--- a/config/config.go
+++ /dev/null
@@ -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"
-)
diff --git a/config/iris.go b/config/iris.go
deleted file mode 100644
index 0de92c74..00000000
--- a/config/iris.go
+++ /dev/null
@@ -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
-}
diff --git a/config/server.go b/config/server.go
deleted file mode 100644
index ba103cf6..00000000
--- a/config/server.go
+++ /dev/null
@@ -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
-}
diff --git a/config/sessions.go b/config/sessions.go
deleted file mode 100644
index 75ee0792..00000000
--- a/config/sessions.go
+++ /dev/null
@@ -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
-}
diff --git a/config/tester.go b/config/tester.go
deleted file mode 100644
index c933a366..00000000
--- a/config/tester.go
+++ /dev/null
@@ -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}
-}
diff --git a/config/websocket.go b/config/websocket.go
deleted file mode 100644
index 3c4fe3fe..00000000
--- a/config/websocket.go
+++ /dev/null
@@ -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
-}
diff --git a/configuration.go b/configuration.go
new file mode 100644
index 00000000..80556ac9
--- /dev/null
+++ b/configuration.go
@@ -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: "",
+ }
+}
diff --git a/configuration_test.go b/configuration_test.go
new file mode 100644
index 00000000..6c42ac42
--- /dev/null
+++ b/configuration_test.go
@@ -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)
+ }
+
+}
diff --git a/context.go b/context.go
index d92d2c49..6a29e03c 100644
--- a/context.go
+++ b/context.go
@@ -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)
diff --git a/http.go b/http.go
index 4e8f826e..98e39f45 100644
--- a/http.go
+++ b/http.go
@@ -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,
}
diff --git a/http_test.go b/http_test.go
index cbfd3fc5..e2f6fbe1 100644
--- a/http_test.go
+++ b/http_test.go
@@ -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()
diff --git a/iris.go b/iris.go
index 93dbd98f..c3d3001b 100644
--- a/iris.go
+++ b/iris.go
@@ -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)
diff --git a/response.go b/response.go
index 46298c12..8310c776 100644
--- a/response.go
+++ b/response.go
@@ -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.
diff --git a/ssh.go b/ssh.go
index 9e0081da..8360de89 100644
--- a/ssh.go
+++ b/ssh.go
@@ -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 {
diff --git a/template.go b/template.go
index 8122ebf0..7d6cdb6e 100644
--- a/template.go
+++ b/template.go
@@ -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)
+}
diff --git a/websocket.go b/websocket.go
index 57d01c9c..cfac6ff9 100644
--- a/websocket.go
+++ b/websocket.go
@@ -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)
})