diff --git a/HISTORY.md b/HISTORY.md index 5eb17995..7096ff60 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -24,7 +24,13 @@ to adapt the new changes to your application, it contains an overview of the new - Template engines (two lines to add, same features as before, except their easier configuration) - Basic middleware, that have been written by me, are transfared to the main repository[/middleware](https://github.com/kataras/iris/tree/v6/middleware) with a lot of improvements to the `recover middleware` (see the next) - `func(http.ResponseWriter, r *http.Request, next http.HandlerFunc)` signature is fully compatible using `iris.ToHandler` helper wrapper func, without any need of custom boilerplate code. So all net/http middleware out there are supported, no need to re-invert the world here, search to the internet and you'll find a suitable to your case. -- Developers can use a `yaml` files for the configuration using the `iris.YAML` function: `app := iris.New(iris.YAML("myconfiguration.yaml"))` + +- Load Configuration from an external file, yaml and toml: + + - [yaml-based](http://www.yaml.org/) configuration file using the `iris.YAML` function: `app := iris.New(iris.YAML("myconfiguration.yaml"))` + - [toml-based](https://github.com/toml-lang/toml) configuration file using the `iris.TOML` function: `app := iris.New(iris.TOML("myconfiguration.toml"))` + + - Add `.Regex` middleware which does path validation using the `regexp` package, i.e `.Regex("param", "[0-9]+$")`. Useful for routers that don't support regex route path validation out-of-the-box. - Websocket additions: `c.Context() *iris.Context`, `ws.GetConnectionsByRoom("room name") []websocket.Connection`, `c.OnLeave(func(roomName string){})`, `c.Values().Set(key,value)/.Get(key).Reset()` (where ws:websocket.Server insance, where c:websocket.Connection instance) @@ -45,8 +51,8 @@ Fixes: Changes: - `context.TemplateString` replaced with `app.Render(w io.Writer, name string, bind interface{}, options ...map[string]interface{}) error)` which gives you more functionality. + ```go -``` import "bytes" // .... app := iris.New() diff --git a/README.md b/README.md index 69c45822..eba4ad6d 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,58 @@ Iris is an efficient and well-designed toolbox with robust set of features.
perfect high-performance web applications
with unlimited potentials and portability.
+If you're coming from Node.js world, this is the expressjs alternative for the Go Programming Language. +
+ +
+Benchmark Wizzard July 21, 2016- Processing Time Horizontal Graph. These third-party source benchmark results are for v5. New (go 1.8) benchmarks will be published on the next techempower's fortune! + +

+Feature Overview +----------- + +- Focus on high performance +- Automatically install and serve certificates from https://letsencrypt.org +- Robust routing and middleware ecosystem +- Build RESTful APIs +- Choose your favorite routes' path syntax between [httprouter](https://github.com/kataras/iris/blob/v6/adaptors/httprouter/_example/main.go) and [gorillamux](https://github.com/kataras/iris/blob/v6/adaptors/gorillamux/_example/main.go) +- Request-Scoped Transactions +- Group API's and subdomains with wildcard support +- Body binding for JSON, XML, Forms, can be extended to use your own custom binders +- More than 50 handy functions to send HTTP responses +- View system: supporting more than 6+ template engines, with prerenders. You can still use your favorite +- Define virtual hosts and (wildcard) subdomains with path level routing +- Graceful shutdown +- Limit request body +- Localization i18N +- Serve static files +- Cache +- Log requests +- Customizable format and output for the logger +- Customizable HTTP errors +- Compression (Gzip) +- Authentication + - OAuth, OAuth2 supporting 27+ popular websites + - JWT + - Basic Authentication + - HTTP Sessions +- Add / Remove trailing slash from the URL with option to redirect +- Redirect requests + - HTTP to HTTPS + - HTTP to HTTPS WWW + - HTTP to HTTPS non WWW + - Non WWW to WWW + - WWW to non WWW +- Highly scalable rich content render (Markdown, JSON, JSONP, XML...) +- Websocket-only API similar to socket.io +- Hot Reload on source code changes +- Typescript integration + Web IDE +- Checks for updates at startup +- Highly customizable +- Feels like you used iris forever, thanks to its Fluent API +- And many others... Installation ----------- diff --git a/configuration.go b/configuration.go index 32e25f99..3a47b945 100644 --- a/configuration.go +++ b/configuration.go @@ -6,7 +6,9 @@ import ( "strconv" "time" + "github.com/BurntSushi/toml" "github.com/imdario/mergo" + "github.com/kataras/go-errors" "gopkg.in/yaml.v2" ) @@ -39,15 +41,18 @@ func (o OptionSet) Set(c *Configuration) { o(c) } -// YAML reads Configuration from a file.yml. +var errConfigurationDecode = errors.New("while trying to decode configuration") + +// YAML reads Configuration from a configuration.yml file. // -// Accepts the absolute path of the file.yml. +// Accepts the absolute path of the configuration.yml. // An error will be shown to the user via panic with the error message. -// Error may occur when the file.yml doesn't exists or is not formatted correctly. +// Error may occur when the configuration.yml doesn't exists or is not formatted correctly. // // Usage: -// 1. `app := iris.New(YAML("myfile.yml"))` -// 2. `app.Set(YAML("myfile.yml"))` +// 1. `app := iris.New(iris.YAML("myconfig.yml"))` +// or +// 2. `app.Set(iris.YAML("myconfig.yml"))` func YAML(filename string) Configuration { c := DefaultConfiguration() @@ -55,23 +60,64 @@ func YAML(filename string) Configuration { // which will try to find the 'filename' from current workind dir too. yamlAbsPath, err := filepath.Abs(filename) if err != nil { - panic("FATAL ERROR .yml.filename to absolute: " + err.Error()) + panic(errConfigurationDecode.AppendErr(err)) } // read the raw contents of the file data, err := ioutil.ReadFile(yamlAbsPath) if err != nil { - panic("FATAL ERROR .yml.ReadFile: " + err.Error()) + panic(errConfigurationDecode.AppendErr(err)) } // put the file's contents as yaml to the default configuration(c) if err := yaml.Unmarshal(data, &c); err != nil { - panic("FATAL ERROR .yml.Unmarshal: " + err.Error()) + panic(errConfigurationDecode.AppendErr(err)) } return c } +// TOML reads Configuration from a toml-compatible document file. +// Read more about toml's implementation at: +// https://github.com/toml-lang/toml +// +// +// Accepts the absolute path of the configuration file. +// An error will be shown to the user via panic with the error message. +// Error may occur when the file doesn't exists or is not formatted correctly. +// +// Usage: +// 1. `app := iris.New(iris.TOML("myconfig.toml"))` +// or +// 2. `app.Set(iris.TOML("myconfig.toml"))` +func TOML(filename string) Configuration { + c := DefaultConfiguration() + + // get the abs + // which will try to find the 'filename' from current workind dir too. + tomlAbsPath, err := filepath.Abs(filename) + if err != nil { + panic(errConfigurationDecode.AppendErr(err)) + } + + // read the raw contents of the file + data, err := ioutil.ReadFile(tomlAbsPath) + if err != nil { + panic(errConfigurationDecode.AppendErr(err)) + } + + // put the file's contents as toml to the default configuration(c) + if _, err := toml.Decode(string(data), &c); err != nil { + panic(errConfigurationDecode.AppendErr(err)) + } + // Author's notes: + // The toml's 'usual thing' for key naming is: the_config_key instead of TheConfigKey + // but I am always prefer to use the specific programming language's syntax + // and the original configuration name fields for external configuration files + // so we do 'toml: "TheConfigKeySameAsTheConfigField" instead. + return c +} + // Configuration the whole configuration for an Iris station instance // these can be passed via options also, look at the top of this file(configuration.go). // Configuration is a valid OptionSetter. @@ -101,20 +147,20 @@ type Configuration struct { // addr only(http://) but the nginx mapper is listening to https:// // // Default comes from iris.Default.Listen/.Serve with iris' listeners (TCP4,UNIX,TLS,LETSENCRYPT). - VScheme string `yaml:"VScheme"` + VScheme string `yaml:"VScheme" toml:"VScheme"` // ReadTimeout is the maximum duration before timing out read of the request. - ReadTimeout time.Duration `yaml:"ReadTimeout"` + ReadTimeout time.Duration `yaml:"ReadTimeout" toml:"ReadTimeout"` // WriteTimeout is the maximum duration before timing out write of the response. - WriteTimeout time.Duration `yaml:"WriteTimeout"` + WriteTimeout time.Duration `yaml:"WriteTimeout" toml:"WriteTimeout"` // MaxHeaderBytes controls the maximum number of bytes the // server will read parsing the request header's keys and // values, including the request line. It does not limit the // size of the request body. // If zero, DefaultMaxHeaderBytes is used. - MaxHeaderBytes int `yaml:"MaxHeaderBytes"` + MaxHeaderBytes int `yaml:"MaxHeaderBytes" toml:"MaxHeaderBytes"` // CheckForUpdates will try to search for newer version of Iris based on the https://github.com/kataras/iris/releases // If a newer version found then the app will ask the he dev/user if want to update the 'x' version @@ -130,7 +176,7 @@ type Configuration struct { // Usage: app := iris.New(iris.Configuration{CheckForUpdates: true}) // // Defaults to false. - CheckForUpdates bool `yaml:"CheckForUpdates"` + CheckForUpdates bool `yaml:"CheckForUpdates" toml:"CheckForUpdates"` // DisablePathCorrection corrects and redirects the requested path to the registered path // for example, if /home/ path is requested but no handler for this Route found, @@ -138,7 +184,7 @@ type Configuration struct { // (permant)redirects the client to the correct path /home // // Defaults to false. - DisablePathCorrection bool `yaml:"disablePathCorrection"` + DisablePathCorrection bool `yaml:"DisablePathCorrection" toml:"DisablePathCorrection"` // EnablePathEscape when is true then its escapes the path, the named parameters (if any). // Change to false it if you want something like this https://github.com/kataras/iris/issues/135 to work @@ -151,12 +197,12 @@ type Configuration struct { // projectName, _ := url.QueryUnescape(c.Param("project"). // // Defaults to false. - EnablePathEscape bool `yaml:"EnablePathEscape"` + EnablePathEscape bool `yaml:"EnablePathEscape" toml:"EnablePathEscape"` // FireMethodNotAllowed if it's true router checks for StatusMethodNotAllowed(405) and // fires the 405 error instead of 404 // Defaults to false. - FireMethodNotAllowed bool `yaml:"FireMethodNotAllowed"` + FireMethodNotAllowed bool `yaml:"FireMethodNotAllowed" toml:"FireMethodNotAllowed"` // DisableBodyConsumptionOnUnmarshal manages the reading behavior of the context's body readers/binders. // If setted to true then it @@ -166,23 +212,23 @@ type Configuration struct { // if this field setted to true then a new buffer will be created to read from and the request body. // The body will not be changed and existing data before the // context.UnmarshalBody/ReadJSON/ReadXML will be not consumed. - DisableBodyConsumptionOnUnmarshal bool `yaml:"DisableBodyConsumptionOnUnmarshal"` + DisableBodyConsumptionOnUnmarshal bool `yaml:"DisableBodyConsumptionOnUnmarshal" toml:"DisableBodyConsumptionOnUnmarshal"` // TimeFormat time format for any kind of datetime parsing // Defaults to "Mon, 02 Jan 2006 15:04:05 GMT". - TimeFormat string `yaml:"TimeFormat"` + TimeFormat string `yaml:"TimeFormat" toml:"TimeFormat"` // Charset character encoding for various rendering // used for templates and the rest of the responses // Defaults to "UTF-8". - Charset string `yaml:"Charset"` + Charset string `yaml:"Charset" toml:"Charset"` // 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 globally, you could just use the third parameter // on context.Render("myfileOrResponse", structBinding{}, iris.RenderOptions{"gzip": true}) // Defaults to false. - Gzip bool `yaml:"Gzip"` + Gzip bool `yaml:"Gzip" toml:"Gzip"` // Other are the custom, dynamic options, can be empty. // This field used only by you to set any app's options you want @@ -197,7 +243,7 @@ type Configuration struct { // app.Adapt(routerAdaptor.New()) // app.Config.Other[iris.RouterNameConfigKey] // - Other map[string]interface{} `yaml:"Other"` + Other map[string]interface{} `yaml:"Other" toml:"Other"` } // RouterNameConfigKey is the optional key that is being registered by router adaptor. diff --git a/configuration_test.go b/configuration_test.go index 1c37a5d9..84e0af4c 100644 --- a/configuration_test.go +++ b/configuration_test.go @@ -103,19 +103,19 @@ func TestConfigurationYAML(t *testing.T) { }() yamlConfigurationContents := ` - VHost: iris-go.com - VScheme: https:// - ReadTimeout: 0 - WriteTimeout: 5s - MaxHeaderBytes: 8096 - CheckForUpdates: true - DisablePathCorrection: false - EnablePathEscape: false - FireMethodNotAllowed: true - DisableBodyConsumptionOnUnmarshal: true - TimeFormat: Mon, 01 Jan 2006 15:04:05 GMT - Charset: UTF-8 - Gzip: true +VHost: iris-go.com +VScheme: https:// +ReadTimeout: 0 +WriteTimeout: 5s +MaxHeaderBytes: 8096 +CheckForUpdates: true +DisablePathCorrection: false +EnablePathEscape: false +FireMethodNotAllowed: true +DisableBodyConsumptionOnUnmarshal: true +TimeFormat: Mon, 01 Jan 2006 15:04:05 GMT +Charset: UTF-8 +Gzip: true ` yamlFile.WriteString(yamlConfigurationContents) @@ -133,7 +133,7 @@ func TestConfigurationYAML(t *testing.T) { } if expected := 0; c.ReadTimeout != time.Duration(expected) { - t.Fatalf("error on TestConfigurationYAML: Expected ReadTimeout %s but got %s", expected, c.ReadTimeout) + t.Fatalf("error on TestConfigurationYAML: Expected ReadTimeout %d but got %s", expected, c.ReadTimeout) } if expected := time.Duration(5 * time.Second); c.WriteTimeout != expected { @@ -141,7 +141,7 @@ func TestConfigurationYAML(t *testing.T) { } if expected := 8096; c.MaxHeaderBytes != expected { - t.Fatalf("error on TestConfigurationYAML: Expected MaxHeaderBytes %s but got %s", expected, c.MaxHeaderBytes) + t.Fatalf("error on TestConfigurationYAML: Expected MaxHeaderBytes %d but got %d", expected, c.MaxHeaderBytes) } if expected := true; c.CheckForUpdates != expected { @@ -177,3 +177,105 @@ func TestConfigurationYAML(t *testing.T) { } } + +func TestConfigurationTOML(t *testing.T) { + // create the key and cert files on the fly, and delete them when this test finished + tomlFile, ferr := ioutil.TempFile("", "configuration.toml") + + if ferr != nil { + t.Fatal(ferr) + } + + defer func() { + tomlFile.Close() + time.Sleep(50 * time.Millisecond) + os.Remove(tomlFile.Name()) + }() + + tomlConfigurationContents := ` +VHost = "iris-go.com" +VScheme = "https://" +ReadTimeout = 0 +# Go's toml doesn't supports implicit time.Duration +# There is a solution: I have to write a custom duration which implements the encoding.TextUnmarshaler +# but to use a custom type for time.Duration is not wise. +# So instead of 5s , we just use the 5 000 000 000 nanoseconds on WriteTimeOut. +WriteTimeout = 5000000000 +MaxHeaderBytes = 8096 +CheckForUpdates = true +DisablePathCorrection = false +EnablePathEscape = false +FireMethodNotAllowed = true +DisableBodyConsumptionOnUnmarshal = true +TimeFormat = "Mon, 01 Jan 2006 15:04:05 GMT" +Charset = "UTF-8" +Gzip = true + +[Other] + # Indentation (tabs and/or spaces) is allowed but not required + MyServerName = "Iris.v6" + + ` + tomlFile.WriteString(tomlConfigurationContents) + filename := tomlFile.Name() + app := New(TOML(filename)) + + c := app.Config + + if expected := "iris-go.com"; c.VHost != expected { + t.Fatalf("error on TestConfigurationTOML: Expected VHost %s but got %s", expected, c.VHost) + } + + if expected := "https://"; c.VScheme != expected { + t.Fatalf("error on TestConfigurationTOML: Expected VScheme %s but got %s", expected, c.VScheme) + } + + if expected := 0; c.ReadTimeout != time.Duration(expected) { + t.Fatalf("error on TestConfigurationTOML: Expected ReadTimeout %d but got %s", expected, c.ReadTimeout) + } + + if expected := time.Duration(5 * time.Second); c.WriteTimeout != expected { + t.Fatalf("error on TestConfigurationTOML: Expected WriteTimeout %s but got %s", expected, c.WriteTimeout) + } + + if expected := 8096; c.MaxHeaderBytes != expected { + t.Fatalf("error on TestConfigurationTOML: Expected MaxHeaderBytes %d but got %d", expected, c.MaxHeaderBytes) + } + + if expected := true; c.CheckForUpdates != expected { + t.Fatalf("error on TestConfigurationTOML: Expected checkForUpdates %v but got %v", expected, c.CheckForUpdates) + } + + if expected := false; c.DisablePathCorrection != expected { + t.Fatalf("error on TestConfigurationTOML: Expected DisablePathCorrection %v but got %v", expected, c.DisablePathCorrection) + } + + if expected := false; c.EnablePathEscape != expected { + t.Fatalf("error on TestConfigurationTOML: Expected EnablePathEscape %v but got %v", expected, c.EnablePathEscape) + } + + if expected := true; c.FireMethodNotAllowed != expected { + t.Fatalf("error on TestConfigurationTOML: Expected FireMethodNotAllowed %v but got %v", expected, c.FireMethodNotAllowed) + } + + if expected := true; c.DisableBodyConsumptionOnUnmarshal != expected { + t.Fatalf("error on TestConfigurationTOML: Expected DisableBodyConsumptionOnUnmarshal %v but got %v", expected, c.DisableBodyConsumptionOnUnmarshal) + } + + if expected := "Mon, 01 Jan 2006 15:04:05 GMT"; c.TimeFormat != expected { + t.Fatalf("error on TestConfigurationTOML: Expected TimeFormat %s but got %s", expected, c.TimeFormat) + } + + if expected := "UTF-8"; c.Charset != expected { + t.Fatalf("error on TestConfigurationTOML: Expected Charset %s but got %s", expected, c.Charset) + } + + if expected := true; c.Gzip != expected { + t.Fatalf("error on TestConfigurationTOML: Expected != %v but got %v", expected, c.Gzip) + } + + if expected := "Iris.v6"; c.Other["MyServerName"] != expected { + t.Fatalf("error on TestConfigurationTOML: Expected(Other) != %v but got %v", expected, c.Gzip) + } + +}