diff --git a/HISTORY.md b/HISTORY.md
index c655ef17..1606a4ce 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,158 @@
# History
+## 3.0.0-beta.3, 3.0.0-beta.4 -> 3.0.0-rc.1
+
+This version took me many days because the whole framework's underline code is rewritten after many many many 'yoga'. Iris is not so small anymore, so I (tried) to organized it a little better. Note that, today, you can just go to [iris.go](https://github.com/kataras/iris/tree/master/iris.go) and [context.go](https://github.com/kataras/iris/tree/master/context/context.go) and look what functions you can use. You had some 'bugs' to subdomains, mail service, basic authentication and logger, these are fixed also, see below...
+
+All [examples](https://github.com/iris-contrib/examples) are updated, and I tested them one by one.
+
+
+Many underline changes but the public API didn't changed much, of course this is relative to the way you use this framework, because that:
+
+- Configuration changes: **0**
+
+- iris.Iris pointer -> **iris.Framework** pointer
+
+- iris.DefaultIris -> **iris.Default**
+- iris.Config() -> **iris.Config** is field now
+- iris.Websocket() -> **iris.Websocket** is field now
+- iris.Logger() -> **iris.Logger** is field now
+- iris.Plugins() -> **iris.Plugins** is field now
+- iris.Server() -> **iris.HTTPServer** is field now
+- iris.Rest() -> **REMOVED**
+
+- iris.Mail() -> **REMOVED**
+- iris.Mail().Send() -> **iris.SendMail()**
+- iris.Templates() -> **REMOVED**
+- iris.Templates().RenderString() -> **iris.TemplateString()**
+
+- iris.StaticHandlerFunc -> **iris.StaticHandler**
+- iris.URIOf() -> **iris.URL()**
+- iris.PathOf() -> **iris.Path()**
+
+- context.RenderString() returned string,error -> **context.TemplateString() returns only string, which is empty on any parse error**
+- context.WriteHTML() -> **context.HTML()**
+- context.HTML() -> **context.RenderWithStatus()**
+
+Entirely new
+
+- -> **iris.ListenUNIX(addr string, socket os.Mode)**
+- -> **context.MustRender, same as Render but send response 500 and logs the error on parse error**
+- -> **context.Log(format string, a...interface{})**
+- -> **context.PostFormMulti(name string) []string**
+- -> **iris.Lookups() []Route**
+- -> **iris.Lookup(routeName string) Route**
+- -> **iris.Plugins.On(eventName string, ...func())** and fire all by **iris.Plugins.Call(eventName)**
+
+- iris.Wildcard() **REMOVED**, subdomains and dynamic(wildcard) subdomains can only be registered with **iris.Party("mysubdomain.") && iris.Party("*.")**
+
+
+Semantic change for static subdomains
+
+**1**
+
+**BEFORE** :
+```go
+apiSubdomain := iris.Party("api.mydomain.com")
+{
+//....
+}
+iris.Listen("mydomain.com:80")
+```
+
+
+**NOW** just subdomain part, no need to duplicate ourselves:
+```go
+apiSubdomain := iris.Party("api.")
+{
+//....
+}
+iris.Listen("mydomain.com:80")
+```
+**2**
+
+Before you couldn't set dynamic subdomains and normal subdomains at the same iris station, now you can.
+**NOW, this is possible**
+
+```go
+/* admin.mydomain.com, and for other subdomains the Party(*.) */
+
+admin := iris.Party("admin.")
+{
+ // admin.mydomain.com
+ admin.Get("/", func(c *iris.Context) {
+ c.Write("INDEX FROM admin.mydomain.com")
+ })
+ // admin.mydomain.com/hey
+ admin.Get("/hey", func(c *iris.Context) {
+ c.Write("HEY FROM admin.mydomain.com/hey")
+ })
+ // admin.mydomain.com/hey2
+ admin.Get("/hey2", func(c *iris.Context) {
+ c.Write("HEY SECOND FROM admin.mydomain.com/hey")
+ })
+}
+
+// other.mydomain.com, otadsadsadsa.mydomain.com and so on....
+dynamicSubdomains := iris.Party("*.")
+{
+ dynamicSubdomains.Get("/", dynamicSubdomainHandler)
+
+ dynamicSubdomains.Get("/something", dynamicSubdomainHandler)
+
+ dynamicSubdomains.Get("/something/:param1", dynamicSubdomainHandlerWithParam)
+}
+```
+
+Minor change for listen
+
+
+**BEFORE you could just listen to a port**
+```go
+iris.Listen("8080")
+```
+**NOW you have set a HOSTNAME:PORT**
+```go
+iris.Listen(":8080")
+```
+
+Relative issues/features: https://github.com/kataras/iris/issues/166 , https://github.com/kataras/iris/issues/176, https://github.com/kataras/iris/issues/183, https://github.com/kataras/iris/issues/184
+
+
+**Plugins**
+
+PreHandle and PostHandle are removed, no need to use them anymore you can take routes by **iris.Lookups()**, but add support for custom event listeners by **iris.Plugins.On("event",func(){})** and fire all callbacks by **iris.Plugins.Call("event")** .
+
+**FOR TESTERS**
+
+**BEFORE** :
+```go
+api := iris.New()
+//...
+
+api.PreListen(config.Server{ListeningAddr: ""})
+
+e := httpexpect.WithConfig(httpexpect.Config{
+ Reporter: httpexpect.NewAssertReporter(t),
+ Client: fasthttpexpect.NewBinder(api.ServeRequest),
+})
+
+```
+
+**NOW**:
+
+```go
+api := iris.New()
+//...
+
+e := httpexpect.WithConfig(httpexpect.Config{
+ Reporter: httpexpect.NewAssertReporter(t),
+ Client: fasthttpexpect.NewBinder(api.NoListen().Handler),
+})
+
+
+```
+
## 3.0.0-beta.2 -> 3.0.0-beta.3
- Complete the Jade Template Engine support, {{ render }} and {{ url }} done also.
diff --git a/README.md b/README.md
index 38d2b960..9b3a112d 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
[Travis]: http://travis-ci.org/kataras/iris
[License Widget]: https://img.shields.io/badge/license-Apache%20License%202.0-E91E63.svg?style=flat-square
[License]: https://github.com/kataras/iris/blob/master/LICENSE
-[Release Widget]: https://img.shields.io/badge/release-v3.0.0--beta.4-blue.svg?style=flat-square
+[Release Widget]: https://img.shields.io/badge/release-v3.0.0--rc.1-blue.svg?style=flat-square
[Release]: https://github.com/kataras/iris/releases
[Gitter Widget]: https://img.shields.io/badge/chat-on%20gitter-00BCD4.svg?style=flat-square
[Gitter]: https://gitter.im/kataras/iris
@@ -18,12 +18,7 @@
[Language]: http://golang.org
[Platform Widget]: https://img.shields.io/badge/platform-Any--OS-gray.svg?style=flat-square
-The fastest backend web framework for Go, provides robust set of features.
-
-```sh
-$ which -a motivation-counter
-Stars | https://github.com/kataras/iris/stargazers
-```
+the fastest backend web framework for Go, provides robust set of features.
[![Benchmark Wizzard Processing Time Horizontal Graph](https://raw.githubusercontent.com/iris-contrib/website/cf71811e6acb2f9bf1e715e25660392bf090b923/assets/benchmark_horizontal_transparent.png)](#benchmarks)
@@ -83,7 +78,7 @@ Docs & Community
- Take a look at the [examples](https://github.com/iris-contrib/examples)
-
+- If for some reason your old code doesn't runs, view the [HISTORY](https://github.com//kataras/iris/tree/master/HISTORY.md)
If you'd like to discuss this package, or ask questions about it, feel free to
@@ -123,7 +118,7 @@ Iris suggests you to use [this](https://github.com/gavv/httpexpect) new suite t
Versioning
------------
-Current: **v3.0.0-beta.4**
+Current: **v3.0.0-rc.1**
> Iris is an active project
diff --git a/bindings/errors.go b/bindings/errors.go
deleted file mode 100644
index 380fbf3e..00000000
--- a/bindings/errors.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package bindings
-
-import "github.com/kataras/iris/errors"
-
-var (
- // ErrNoForm returns an error with message: 'Request has no any valid form'
- ErrNoForm = errors.New("Request has no any valid form")
- // ErrWriteJSON returns an error with message: 'Before JSON be written to the body, JSON Encoder returned an error. Trace: +specific error'
- ErrWriteJSON = errors.New("Before JSON be written to the body, JSON Encoder returned an error. Trace: %s")
- // ErrRenderMarshalled returns an error with message: 'Before +type Rendering, MarshalIndent retured an error. Trace: +specific error'
- ErrRenderMarshalled = errors.New("Before +type Rendering, MarshalIndent returned an error. Trace: %s")
- // ErrReadBody returns an error with message: 'While trying to read +type from the request body. Trace +specific error'
- ErrReadBody = errors.New("While trying to read %s from the request body. Trace %s")
-)
diff --git a/bindings/form.go b/bindings/form.go
deleted file mode 100644
index ddbcb837..00000000
--- a/bindings/form.go
+++ /dev/null
@@ -1,420 +0,0 @@
-// Package bindings source code from https://github.com/monoculum/formame.
-package bindings
-
-import (
- "encoding"
- "errors"
- "fmt"
- "net/url"
- "reflect"
- "strconv"
- "strings"
- "time"
-
- "github.com/kataras/iris/context"
-)
-
-const tagName = "form"
-
-// A pathMap holds the values of a map with its key and values correspondent
-type pathMap struct {
- m reflect.Value
-
- key string
- value reflect.Value
-
- path string
-}
-
-// a pathMaps holds the values for each key
-type pathMaps []*pathMap
-
-// find find and get the value by the given key
-func (ma pathMaps) find(id reflect.Value, key string) *pathMap {
- for _, v := range ma {
- if v.m == id && v.key == key {
- return v
- }
- }
- return nil
-}
-
-// A decoder holds the values from form, the 'reflect' value of main struct
-// and the 'reflect' value of current path
-type decoder struct {
- main reflect.Value
-
- curr reflect.Value
- value string
- values []string
-
- path string
- field string
- index int
-
- maps pathMaps
-}
-
-// Decode decodes the url.Values into a element that must be a pointer to a type provided by argument
-func Decode(vs url.Values, dst interface{}) error {
- main := reflect.ValueOf(dst)
- if main.Kind() != reflect.Ptr {
- return fmt.Errorf(tagName+": the value passed for decode is not a pointer but a %v", main.Kind())
- }
-
- dec := &decoder{main: main.Elem()}
-
- // iterate over the form's values and decode it
- for k, v := range vs {
- dec.path = k
- dec.field = k
- dec.values = v
- dec.value = v[0]
- if dec.value != "" {
- if err := dec.begin(); err != nil {
- return err
- }
- }
- }
- // set values of each maps
- for _, v := range dec.maps {
- key := v.m.Type().Key()
- switch key.Kind() {
- case reflect.String:
- // the key is a string
- v.m.SetMapIndex(reflect.ValueOf(v.key), v.value)
- default:
- // must to implement the TextUnmarshaler interface for to can to decode the map's key
- var val reflect.Value
-
- if key.Kind() == reflect.Ptr {
- val = reflect.New(key.Elem())
- } else {
- val = reflect.New(key).Elem()
- }
-
- dec.value = v.key
- if ok, err := dec.unmarshalText(val); !ok {
- return fmt.Errorf(tagName+": the key with %s type (%v) in the path %v should implements the TextUnmarshaler interface for to can decode it", key, v.m.Type(), v.path)
- } else if err != nil {
- return fmt.Errorf(tagName+": an error has occured in the UnmarshalText method for type %s: %s", key, err)
- }
-
- v.m.SetMapIndex(val, v.value)
- }
- }
-
- dec.maps = make(pathMaps, 0)
- return nil
-}
-
-// begin prepare the current path to walk through it
-func (dec *decoder) begin() (err error) {
- dec.curr = dec.main
- fields := strings.Split(dec.field, ".")
- for i, field := range fields {
- b := strings.IndexAny(field, "[")
- if b != -1 {
- // is a array
- e := strings.IndexAny(field, "]")
- if e == -1 {
- return errors.New(tagName + ": bad syntax array")
- }
- dec.field = field[:b]
- if dec.index, err = strconv.Atoi(field[b+1 : e]); err != nil {
- return errors.New(tagName + ": the index of array is not a number")
- }
- if len(fields) == i+1 {
- return dec.end()
- }
- if err = dec.walk(); err != nil {
- return
- }
- } else {
- // not is a array
- dec.field = field
- dec.index = -1
- if len(fields) == i+1 {
- return dec.end()
- }
- if err = dec.walk(); err != nil {
- return
- }
- }
- }
- return
-}
-
-// walk traverses the current path until to the last field
-func (dec *decoder) walk() error {
- // check if is a struct or map
- switch dec.curr.Kind() {
- case reflect.Struct:
- if err := dec.findStructField(); err != nil {
- return err
- }
- case reflect.Map:
- dec.currentMap()
- }
- // check if the struct or map is a interface
- if dec.curr.Kind() == reflect.Interface {
- dec.curr = dec.curr.Elem()
- }
- // check if the struct or map is a pointer
- if dec.curr.Kind() == reflect.Ptr {
- if dec.curr.IsNil() {
- dec.curr.Set(reflect.New(dec.curr.Type().Elem()))
- }
- dec.curr = dec.curr.Elem()
- }
- // finally, check if there are access to slice/array or not...
- if dec.index != -1 {
- switch dec.curr.Kind() {
- case reflect.Slice, reflect.Array:
- if dec.curr.Len() <= dec.index {
- dec.expandSlice(dec.index + 1)
- }
- dec.curr = dec.curr.Index(dec.index)
- default:
- return fmt.Errorf(tagName+": the field \"%v\" in path \"%v\" has a index for array but it is not", dec.field, dec.path)
- }
- }
- return nil
-}
-
-// end finds the last field for decode its value correspondent
-func (dec *decoder) end() error {
- if dec.curr.Kind() == reflect.Struct {
- if err := dec.findStructField(); err != nil {
- return err
- }
- }
- if dec.value == "" {
- return nil
- }
- return dec.decode()
-}
-
-// decode sets the value in the last field found by end function
-func (dec *decoder) decode() error {
- if ok, err := dec.unmarshalText(dec.curr); ok || err != nil {
- return err
- }
-
- switch dec.curr.Kind() {
- case reflect.Map:
- dec.currentMap()
- return dec.decode()
- case reflect.Slice, reflect.Array:
- if dec.index == -1 {
- // not has index, so to decode all values in the slice/array
- dec.expandSlice(len(dec.values))
- tmp := dec.curr
- for i, v := range dec.values {
- dec.curr = tmp.Index(i)
- dec.value = v
- if err := dec.decode(); err != nil {
- return err
- }
- }
- } else {
- // has index, so to decode value by index indicated
- if dec.curr.Len() <= dec.index {
- dec.expandSlice(dec.index + 1)
- }
- dec.curr = dec.curr.Index(dec.index)
- return dec.decode()
- }
- case reflect.String:
- dec.curr.SetString(dec.value)
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- if num, err := strconv.ParseInt(dec.value, 10, 64); err != nil {
- return fmt.Errorf(tagName+": the value of field \"%v\" in path \"%v\" should be a valid signed integer number", dec.field, dec.path)
- } else {
- dec.curr.SetInt(num)
- }
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- if num, err := strconv.ParseUint(dec.value, 10, 64); err != nil {
- return fmt.Errorf(tagName+": the value of field \"%v\" in path \"%v\" should be a valid unsigned integer number", dec.field, dec.path)
- } else {
- dec.curr.SetUint(num)
- }
- case reflect.Float32, reflect.Float64:
- if num, err := strconv.ParseFloat(dec.value, dec.curr.Type().Bits()); err != nil {
- return fmt.Errorf(tagName+": the value of field \"%v\" in path \"%v\" should be a valid float number", dec.field, dec.path)
- } else {
- dec.curr.SetFloat(num)
- }
- case reflect.Bool:
- switch dec.value {
- case "true", "on", "1":
- dec.curr.SetBool(true)
- case "false", "off", "0":
- dec.curr.SetBool(false)
- default:
- return fmt.Errorf(tagName+": the value of field \"%v\" in path \"%v\" is not a valid boolean", dec.field, dec.path)
- }
- case reflect.Interface:
- dec.curr.Set(reflect.ValueOf(dec.value))
- case reflect.Ptr:
- dec.curr.Set(reflect.New(dec.curr.Type().Elem()))
- dec.curr = dec.curr.Elem()
- return dec.decode()
- case reflect.Struct:
- switch dec.curr.Interface().(type) {
- case time.Time:
- t, err := time.Parse("2006-01-02", dec.value)
- if err != nil {
- return fmt.Errorf(tagName+": the value of field \"%v\" in path \"%v\" is not a valid datetime", dec.field, dec.path)
- }
- dec.curr.Set(reflect.ValueOf(t))
- case url.URL:
- u, err := url.Parse(dec.value)
- if err != nil {
- return fmt.Errorf(tagName+": the value of field \"%v\" in path \"%v\" is not a valid url", dec.field, dec.path)
- }
- dec.curr.Set(reflect.ValueOf(*u))
- default:
- return fmt.Errorf(tagName+": not supported type for field \"%v\" in path \"%v\"", dec.field, dec.path)
- }
- default:
- return fmt.Errorf(tagName+": not supported type for field \"%v\" in path \"%v\"", dec.field, dec.path)
- }
-
- return nil
-}
-
-// findField finds a field by its name, if it is not found,
-// then retry the search examining the tag "form" of every field of struct
-func (dec *decoder) findStructField() error {
- var anon reflect.Value
-
- num := dec.curr.NumField()
- for i := 0; i < num; i++ {
- field := dec.curr.Type().Field(i)
- if field.Name == dec.field {
- // check if the field's name is equal
- dec.curr = dec.curr.Field(i)
- return nil
- } else if field.Anonymous {
- // if the field is a anonymous struct, then iterate over its fields
- tmp := dec.curr
- dec.curr = dec.curr.FieldByIndex(field.Index)
- if err := dec.findStructField(); err != nil {
- dec.curr = tmp
- continue
- }
- // field in anonymous struct is found,
- // but first it should found the field in the rest of struct
- // (a field with same name in the current struct should have preference over anonymous struct)
- anon = dec.curr
- dec.curr = tmp
- } else if dec.field == field.Tag.Get(tagName) {
-
- dec.curr = dec.curr.Field(i)
- return nil
- }
- }
-
- if anon.IsValid() {
- dec.curr = anon
- return nil
- }
-
- return fmt.Errorf(tagName+": not found the field \"%v\" in the path \"%v\"", dec.field, dec.path)
-}
-
-// expandSlice expands the length and capacity of the current slice
-func (dec *decoder) expandSlice(length int) {
- n := reflect.MakeSlice(dec.curr.Type(), length, length)
- reflect.Copy(n, dec.curr)
- dec.curr.Set(n)
-}
-
-// currentMap gets in d.curr the map concrete for decode the current value
-func (dec *decoder) currentMap() {
- n := dec.curr.Type()
- if dec.curr.IsNil() {
- dec.curr.Set(reflect.MakeMap(n))
- m := reflect.New(n.Elem()).Elem()
- dec.maps = append(dec.maps, &pathMap{dec.curr, dec.field, m, dec.path})
- dec.curr = m
- } else if a := dec.maps.find(dec.curr, dec.field); a == nil {
- m := reflect.New(n.Elem()).Elem()
- dec.maps = append(dec.maps, &pathMap{dec.curr, dec.field, m, dec.path})
- dec.curr = m
- } else {
- dec.curr = a.value
- }
-}
-
-var (
- timeType = reflect.TypeOf(time.Time{})
- timePType = reflect.TypeOf(&time.Time{})
-)
-
-// unmarshalText returns a boolean and error. The boolean is true if the
-// value implements TextUnmarshaler, and false if not.
-func (dec *decoder) unmarshalText(v reflect.Value) (bool, error) {
- // skip if the type is time.Time
- n := v.Type()
- if n.ConvertibleTo(timeType) || n.ConvertibleTo(timePType) {
- return false, nil
- }
- // check if implements the interface
- m, ok := v.Interface().(encoding.TextUnmarshaler)
- addr := v.CanAddr()
- if !ok && !addr {
- return false, nil
- } else if addr {
- return dec.unmarshalText(v.Addr())
- }
- // return result
- err := m.UnmarshalText([]byte(dec.value))
- return true, err
-}
-
-// BindForm binds the formObject with the form data
-// it supports any kind of struct
-func BindForm(ctx context.IContext, formObject interface{}) error {
- reqCtx := ctx.GetRequestCtx()
- // first check if we have multipart form
- multipartForm, err := reqCtx.MultipartForm()
- if err == nil {
- //we have multipart form
- return ErrReadBody.With(Decode(multipartForm.Value, formObject))
- }
- // if no multipart and post arguments ( means normal form)
-
- if reqCtx.PostArgs().Len() == 0 && reqCtx.QueryArgs().Len() == 0 {
- return ErrReadBody.With(ErrNoForm.Return())
- }
-
- form := make(map[string][]string, reqCtx.PostArgs().Len()+reqCtx.QueryArgs().Len())
-
- reqCtx.PostArgs().VisitAll(func(k []byte, v []byte) {
- key := string(k)
- value := string(v)
- // for slices
- if form[key] != nil {
- form[key] = append(form[key], value)
- } else {
- form[key] = []string{value}
- }
-
- })
-
- reqCtx.QueryArgs().VisitAll(func(k []byte, v []byte) {
- key := string(k)
- value := string(v)
- // for slices
- if form[key] != nil {
- form[key] = append(form[key], value)
- } else {
- form[key] = []string{value}
- }
- })
-
- return ErrReadBody.With(Decode(form, formObject))
-
-}
diff --git a/bindings/json.go b/bindings/json.go
deleted file mode 100644
index ec238253..00000000
--- a/bindings/json.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package bindings
-
-import (
- "encoding/json"
-
- "io"
- "strings"
-
- "github.com/kataras/iris/context"
-)
-
-// BindJSON reads JSON from request's body
-func BindJSON(ctx context.IContext, jsonObject interface{}) error {
- data := ctx.GetRequestCtx().Request.Body()
-
- decoder := json.NewDecoder(strings.NewReader(string(data)))
- err := decoder.Decode(jsonObject)
-
- //err != nil fix by @shiena
- if err != nil && err != io.EOF {
- return ErrReadBody.Format("JSON", err.Error())
- }
-
- return nil
-}
diff --git a/bindings/xml.go b/bindings/xml.go
deleted file mode 100644
index 29848f76..00000000
--- a/bindings/xml.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package bindings
-
-import (
- "encoding/xml"
- "io"
- "strings"
-
- "github.com/kataras/iris/context"
-)
-
-// BindXML reads XML from request's body
-func BindXML(ctx context.IContext, xmlObject interface{}) error {
- data := ctx.GetRequestCtx().Request.Body()
-
- decoder := xml.NewDecoder(strings.NewReader(string(data)))
- err := decoder.Decode(xmlObject)
- //err != nil fix by @shiena
- if err != nil && err != io.EOF {
- return ErrReadBody.Format("XML", err.Error())
- }
-
- return nil
-}
diff --git a/branch.go b/branch.go
deleted file mode 100644
index b7951c03..00000000
--- a/branch.go
+++ /dev/null
@@ -1,457 +0,0 @@
-// Copyright (c) 2013 Julien Schmidt, Copyright (c) 2016 Gerasimos Maropoulos,
-
-package iris
-
-import (
- "bytes"
- "strings"
-
- "github.com/kataras/iris/utils"
-)
-
-const (
- isStatic BranchCase = iota
- isRoot
- hasParams
- matchEverything
-)
-
-type (
- // PathParameter is a struct which contains Key and Value, used for named path parameters
- PathParameter struct {
- Key string
- Value string
- }
-
- // PathParameters type for a slice of PathParameter
- // Tt's a slice of PathParameter type, because it's faster than map
- PathParameters []PathParameter
-
- // BranchCase is the type which the type of Branch using in order to determinate what type (parameterized, anything, static...) is the perticular node
- BranchCase uint8
-
- // IBranch is the interface which the type Branch must implement
- IBranch interface {
- AddBranch(string, Middleware)
- AddNode(uint8, string, string, Middleware)
- GetBranch(string, PathParameters) (Middleware, PathParameters, bool)
- GivePrecedenceTo(index int) int
- }
-
- // Branch is the node of a tree of the routes,
- // in order to learn how this is working, google 'trie' or watch this lecture: https://www.youtube.com/watch?v=uhAUk63tLRM
- // this method is used by the BSD's kernel also
- Branch struct {
- part string
- BranchCase BranchCase
- hasWildNode bool
- tokens string
- nodes []*Branch
- middleware Middleware
- precedence uint64
- paramsLen uint8
- }
-)
-
-var _ IBranch = &Branch{}
-
-// Get returns a value from a key inside this Parameters
-// If no parameter with this key given then it returns an empty string
-func (params PathParameters) Get(key string) string {
- for _, p := range params {
- if p.Key == key {
- return p.Value
- }
- }
- return ""
-}
-
-// String returns a string implementation of all parameters that this PathParameters object keeps
-// hasthe form of key1=value1,key2=value2...
-func (params PathParameters) String() string {
- var buff bytes.Buffer
- for i := range params {
- buff.WriteString(params[i].Key)
- buff.WriteString("=")
- buff.WriteString(params[i].Value)
- if i < len(params)-1 {
- buff.WriteString(",")
- }
-
- }
- return buff.String()
-}
-
-// ParseParams receives a string and returns PathParameters (slice of PathParameter)
-// received string must have this form: key1=value1,key2=value2...
-func ParseParams(str string) PathParameters {
- _paramsstr := strings.Split(str, ",")
- if len(_paramsstr) == 0 {
- return nil
- }
-
- params := make(PathParameters, 0) // PathParameters{}
-
- // for i := 0; i < len(_paramsstr); i++ {
- for i := range _paramsstr {
- idxOfEq := strings.IndexRune(_paramsstr[i], '=')
- if idxOfEq == -1 {
- //error
- return nil
- }
-
- key := _paramsstr[i][:idxOfEq]
- val := _paramsstr[i][idxOfEq+1:]
- params = append(params, PathParameter{key, val})
- }
- return params
-}
-
-// GetParamsLen returns the parameters length from a given path
-func GetParamsLen(path string) uint8 {
- var n uint
- for i := 0; i < len(path); i++ {
- if path[i] != ':' && path[i] != '*' { // ParameterStartByte & MatchEverythingByte
- continue
- }
- n++
- }
- if n >= 255 {
- return 255
- }
- return uint8(n)
-}
-
-// AddBranch adds a branch to the existing branch or to the tree if no branch has the prefix of
-func (b *Branch) AddBranch(path string, middleware Middleware) {
- fullPath := path
- b.precedence++
- numParams := GetParamsLen(path)
-
- if len(b.part) > 0 || len(b.nodes) > 0 {
- loop:
- for {
- if numParams > b.paramsLen {
- b.paramsLen = numParams
- }
-
- i := 0
- max := utils.FindLower(len(path), len(b.part))
- for i < max && path[i] == b.part[i] {
- i++
- }
-
- if i < len(b.part) {
- node := Branch{
- part: b.part[i:],
- hasWildNode: b.hasWildNode,
- tokens: b.tokens,
- nodes: b.nodes,
- middleware: b.middleware,
- precedence: b.precedence - 1,
- }
-
- for i := range node.nodes {
- if node.nodes[i].paramsLen > node.paramsLen {
- node.paramsLen = node.nodes[i].paramsLen
- }
- }
-
- b.nodes = []*Branch{&node}
- b.tokens = string([]byte{b.part[i]})
- b.part = path[:i]
- b.middleware = nil
- b.hasWildNode = false
- }
-
- if i < len(path) {
- path = path[i:]
-
- if b.hasWildNode {
- b = b.nodes[0]
- b.precedence++
-
- if numParams > b.paramsLen {
- b.paramsLen = numParams
- }
- numParams--
-
- if len(path) >= len(b.part) && b.part == path[:len(b.part)] {
-
- if len(b.part) >= len(path) || path[len(b.part)] == '/' {
- continue loop
- }
- }
-
- return
- }
-
- c := path[0]
-
- if b.BranchCase == hasParams && c == '/' && len(b.nodes) == 1 {
- b = b.nodes[0]
- b.precedence++
- continue loop
- }
- //we need the i here to be re-setting, so use the same i variable as we declare it on line 176
- for i := range b.tokens {
- if c == b.tokens[i] {
- i = b.GivePrecedenceTo(i)
- b = b.nodes[i]
- continue loop
- }
- }
-
- if c != ParameterStartByte && c != MatchEverythingByte {
-
- b.tokens += string([]byte{c})
- node := &Branch{
- paramsLen: numParams,
- }
- b.nodes = append(b.nodes, node)
- b.GivePrecedenceTo(len(b.tokens) - 1)
- b = node
- }
- b.AddNode(numParams, path, fullPath, middleware)
- return
-
- } else if i == len(path) {
- if b.middleware != nil {
- return
- }
- b.middleware = middleware
- }
- return
- }
- } else {
- b.AddNode(numParams, path, fullPath, middleware)
- b.BranchCase = isRoot
- }
-}
-
-// AddNode adds a branch as children to other Branch
-func (b *Branch) AddNode(numParams uint8, path string, fullPath string, middleware Middleware) {
- var offset int
-
- for i, max := 0, len(path); numParams > 0; i++ {
- c := path[i]
- if c != ParameterStartByte && c != MatchEverythingByte {
- continue
- }
-
- end := i + 1
- for end < max && path[end] != '/' {
- switch path[end] {
- case ParameterStartByte, MatchEverythingByte:
-
- default:
- end++
- }
- }
-
- if len(b.nodes) > 0 {
- return
- }
-
- if end-i < 2 {
- return
- }
-
- if c == ParameterStartByte {
-
- if i > 0 {
- b.part = path[offset:i]
- offset = i
- }
-
- child := &Branch{
- BranchCase: hasParams,
- paramsLen: numParams,
- }
- b.nodes = []*Branch{child}
- b.hasWildNode = true
- b = child
- b.precedence++
- numParams--
-
- if end < max {
- b.part = path[offset:end]
- offset = end
-
- child := &Branch{
- paramsLen: numParams,
- precedence: 1,
- }
- b.nodes = []*Branch{child}
- b = child
- }
-
- } else {
- if end != max || numParams > 1 {
- return
- }
-
- if len(b.part) > 0 && b.part[len(b.part)-1] == '/' {
- return
- }
-
- i--
- if path[i] != '/' {
- return
- }
-
- b.part = path[offset:i]
-
- child := &Branch{
- hasWildNode: true,
- BranchCase: matchEverything,
- paramsLen: 1,
- }
- b.nodes = []*Branch{child}
- b.tokens = string(path[i])
- b = child
- b.precedence++
-
- child = &Branch{
- part: path[i:],
- BranchCase: matchEverything,
- paramsLen: 1,
- middleware: middleware,
- precedence: 1,
- }
- b.nodes = []*Branch{child}
-
- return
- }
- }
-
- b.part = path[offset:]
- b.middleware = middleware
-}
-
-// GetBranch is used by the Router, it finds and returns the correct branch for a path
-func (b *Branch) GetBranch(path string, _params PathParameters) (middleware Middleware, params PathParameters, mustRedirect bool) {
- params = _params
-loop:
- for {
- if len(path) > len(b.part) {
- if path[:len(b.part)] == b.part {
- path = path[len(b.part):]
-
- if !b.hasWildNode {
- c := path[0]
- for i := range b.tokens {
- if c == b.tokens[i] {
- b = b.nodes[i]
- continue loop
- }
- }
-
- mustRedirect = (path == Slash && b.middleware != nil)
- return
- }
-
- b = b.nodes[0]
- switch b.BranchCase {
- case hasParams:
-
- end := 0
- for end < len(path) && path[end] != '/' {
- end++
- }
-
- if cap(params) < int(b.paramsLen) {
- params = make(PathParameters, 0, b.paramsLen)
- }
- i := len(params)
- params = params[:i+1]
- params[i].Key = b.part[1:]
- params[i].Value = path[:end]
-
- if end < len(path) {
- if len(b.nodes) > 0 {
- path = path[end:]
- b = b.nodes[0]
- continue loop
- }
-
- mustRedirect = (len(path) == end+1)
- return
- }
-
- if middleware = b.middleware; middleware != nil {
- return
- } else if len(b.nodes) == 1 {
- b = b.nodes[0]
- mustRedirect = (b.part == Slash && b.middleware != nil)
- }
-
- return
-
- case matchEverything:
- if cap(params) < int(b.paramsLen) {
- params = make(PathParameters, 0, b.paramsLen)
- }
- i := len(params)
- params = params[:i+1]
- params[i].Key = b.part[2:]
- params[i].Value = path
-
- middleware = b.middleware
- return
-
- default:
- return
- }
- }
- } else if path == b.part {
- if middleware = b.middleware; middleware != nil {
- return
- }
-
- if path == Slash && b.hasWildNode && b.BranchCase != isRoot {
- mustRedirect = true
- return
- }
-
- for i := range b.tokens {
- if b.tokens[i] == '/' {
- b = b.nodes[i]
- mustRedirect = (len(b.part) == 1 && b.middleware != nil) ||
- (b.BranchCase == matchEverything && b.nodes[0].middleware != nil)
- return
- }
- }
-
- return
- }
-
- mustRedirect = (path == Slash) ||
- (len(b.part) == len(path)+1 && b.part[len(path)] == '/' &&
- path == b.part[:len(b.part)-1] && b.middleware != nil)
- return
- }
-}
-
-// GivePrecedenceTo just adds the priority of this branch by an index
-func (b *Branch) GivePrecedenceTo(index int) int {
- b.nodes[index].precedence++
- _precedence := b.nodes[index].precedence
-
- newindex := index
- for newindex > 0 && b.nodes[newindex-1].precedence < _precedence {
- tmpN := b.nodes[newindex-1]
- b.nodes[newindex-1] = b.nodes[newindex]
- b.nodes[newindex] = tmpN
-
- newindex--
- }
-
- if newindex != index {
- b.tokens = b.tokens[:newindex] +
- b.tokens[index:index+1] +
- b.tokens[newindex:index] + b.tokens[index+1:]
- }
-
- return newindex
-}
diff --git a/config/iris.go b/config/iris.go
index 868f0783..a0d4370f 100644
--- a/config/iris.go
+++ b/config/iris.go
@@ -4,21 +4,24 @@ import (
"github.com/imdario/mergo"
)
-// DefaultProfilePath is the default profile(http debug) path which is /debug/pprof
-const DefaultProfilePath = "/debug/pprof"
+// Default values for base Iris conf
+const (
+ DefaultDisablePathCorrection = false
+ DefaultDisablePathEscape = false
+)
type (
// Iris configs for the station
// All fields can be changed before server's listen except the DisablePathCorrection field
//
// MaxRequestBodySize is the only options that can be changed after server listen -
- // using Config().MaxRequestBodySize = ...
+ // using Config.MaxRequestBodySize = ...
// Render's rest config can be changed after declaration but before server's listen -
- // using Config().Render.Rest...
+ // using Config.Render.Rest...
// Render's Template config can be changed after declaration but before server's listen -
- // using Config().Render.Template...
+ // using Config.Render.Template...
// Sessions config can be changed after declaration but before server's listen -
- // using Config().Sessions...
+ // using Config.Sessions...
// and so on...
Iris struct {
@@ -56,40 +59,57 @@ type (
// By default request body size is -1, unlimited.
MaxRequestBodySize int64
- // Profile set to true to enable web pprof (debug profiling)
- // Default is false, enabling makes available these 7 routes:
- // /debug/pprof/cmdline
- // /debug/pprof/profile
- // /debug/pprof/symbol
- // /debug/pprof/goroutine
- // /debug/pprof/heap
- // /debug/pprof/threadcreate
- // /debug/pprof/pprof/block
- Profile bool
-
- // ProfilePath change it if you want other url path than the default
- // Default is /debug/pprof , which means yourhost.com/debug/pprof
+ // 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
// Logger the configuration for the logger
- // Iris logs ONLY errors and the banner if enabled
+ // Iris logs ONLY SEMANTIC errors and the banner if enabled
Logger Logger
- // Sessions the config for sessions
- // contains 3(three) properties
- // Provider: (look /sessions/providers)
- // Secret: cookie's name (string)
- // Life: cookie life (time.Duration)
+ // Sessions contains the configs for sessions
Sessions Sessions
// Render contains the configs for template and rest configuration
Render Render
// Websocket contains the configs for Websocket's server integration
- Websocket Websocket
+ Websocket *Websocket
- // Mail contains the config for the mail sender service
+ // Mail contains the configs for the mail sender service
Mail Mail
+
+ // Server contains the configs for the http server
+ // Server configs are the only one which are setted inside base Iris package (from Listen, ListenTLS, ListenUNIX) NO from users
+ //
+ // this field is useful only when you need to READ which is the server's address, certfile & keyfile or unix's mode.
+ //
+ Server Server
}
// Render struct keeps organise all configuration about rendering, templates and rest currently.
@@ -117,16 +137,17 @@ func DefaultRender() Render {
// Default returns the default configuration for the Iris staton
func Default() Iris {
return Iris{
- DisablePathCorrection: false,
- DisablePathEscape: false,
+ DisablePathCorrection: DefaultDisablePathCorrection,
+ DisablePathEscape: DefaultDisablePathEscape,
DisableBanner: false,
MaxRequestBodySize: -1,
- ProfilePath: DefaultProfilePath,
+ ProfilePath: "",
Logger: DefaultLogger(),
Sessions: DefaultSessions(),
Render: DefaultRender(),
Websocket: DefaultWebsocket(),
Mail: DefaultMail(),
+ Server: DefaultServer(),
}
}
diff --git a/config/server.go b/config/server.go
index 2e1b22c7..52c5b345 100644
--- a/config/server.go
+++ b/config/server.go
@@ -7,8 +7,8 @@ import (
)
const (
- // DefaultServerAddr the default server addr
- DefaultServerAddr = ":8080"
+ // DefaultServerAddr the default server addr which is: 127.0.0.1:8080
+ DefaultServerAddr = "127.0.0.1:8080"
)
// ServerName the response header of the 'Server' value when writes to the client
diff --git a/config/websocket.go b/config/websocket.go
index be20ca3b..1ada5caa 100644
--- a/config/websocket.go
+++ b/config/websocket.go
@@ -43,8 +43,8 @@ type Websocket struct {
}
// DefaultWebsocket returns the default config for iris-ws websocket package
-func DefaultWebsocket() Websocket {
- return Websocket{
+func DefaultWebsocket() *Websocket {
+ return &Websocket{
WriteTimeout: DefaultWriteTimeout,
PongTimeout: DefaultPongTimeout,
PingPeriod: DefaultPingPeriod,
@@ -55,11 +55,11 @@ func DefaultWebsocket() Websocket {
}
// Merge merges the default with the given config and returns the result
-func (c Websocket) Merge(cfg []Websocket) (config Websocket) {
+func (c *Websocket) Merge(cfg []*Websocket) (config *Websocket) {
if len(cfg) > 0 {
config = cfg[0]
- mergo.Merge(&config, c)
+ mergo.Merge(config, c)
} else {
_default := c
config = _default
@@ -69,10 +69,10 @@ func (c Websocket) Merge(cfg []Websocket) (config Websocket) {
}
// MergeSingle merges the default with the given config and returns the result
-func (c Websocket) MergeSingle(cfg Websocket) (config Websocket) {
+func (c *Websocket) MergeSingle(cfg *Websocket) (config *Websocket) {
config = cfg
- mergo.Merge(&config, c)
+ mergo.Merge(config, c)
return
}
diff --git a/context.go b/context.go
index 846ca775..f521bfd1 100644
--- a/context.go
+++ b/context.go
@@ -6,40 +6,72 @@ files: context_renderer.go, context_storage.go, context_request.go, context_resp
package iris
import (
+ "bufio"
+ "encoding/base64"
+ "encoding/json"
+ "encoding/xml"
+ "fmt"
+ "html/template"
+ "io"
+ "net"
+ "os"
+ "path"
"reflect"
"runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+ "github.com/iris-contrib/formBinder"
+ "github.com/kataras/iris/config"
"github.com/kataras/iris/context"
+ "github.com/kataras/iris/errors"
"github.com/kataras/iris/sessions/store"
+ "github.com/kataras/iris/utils"
+ "github.com/klauspost/compress/gzip"
"github.com/valyala/fasthttp"
)
const (
// DefaultUserAgent default to 'iris' but it is not used anywhere yet
- DefaultUserAgent = "iris"
+ defaultUserAgent = "iris"
// ContentType represents the header["Content-Type"]
- ContentType = "Content-Type"
+ contentType = "Content-Type"
// ContentLength represents the header["Content-Length"]
- ContentLength = "Content-Length"
+ contentLength = "Content-Length"
// ContentHTML is the string of text/html response headers
- ContentHTML = "text/html"
+ contentHTML = "text/html"
// ContentBINARY is the string of application/octet-stream response headers
- ContentBINARY = "application/octet-stream"
+ contentBINARY = "application/octet-stream"
// LastModified "Last-Modified"
- LastModified = "Last-Modified"
+ lastModified = "Last-Modified"
// IfModifiedSince "If-Modified-Since"
- IfModifiedSince = "If-Modified-Since"
+ ifModifiedSince = "If-Modified-Since"
// ContentDisposition "Content-Disposition"
- ContentDisposition = "Content-Disposition"
-
- // TimeFormat default time format for any kind of datetime parsing
- TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
+ contentDisposition = "Content-Disposition"
// stopExecutionPosition used inside the Context, is the number which shows us that the context's middleware manualy stop the execution
stopExecutionPosition = 255
)
+// 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 (
+ errTemplateExecute = errors.New("Unable to execute a template. Trace: %s")
+ errFlashNotFound = errors.New("Unable to get flash message. Trace: Cookie does not exists")
+ errSessionNil = errors.New("Unable to set session, Config().Session.Provider is nil, please refer to the docs!")
+ errNoForm = errors.New("Request has no any valid form")
+ errWriteJSON = errors.New("Before JSON be written to the body, JSON Encoder returned an error. Trace: %s")
+ errRenderMarshalled = errors.New("Before +type Rendering, MarshalIndent returned an error. Trace: %s")
+ errReadBody = errors.New("While trying to read %s from the request body. Trace %s")
+ errServeContent = errors.New("While trying to serve content to the client. Trace %s")
+)
+
type (
// Map is just a conversion for a map[string]interface{}
Map map[string]interface{}
@@ -47,8 +79,8 @@ type (
// it is not good practice to use this object in goroutines, for these cases use the .Clone()
Context struct {
*fasthttp.RequestCtx
- Params PathParameters
- station *Iris
+ Params PathParameters
+ framework *Framework
//keep track all registed middleware (handlers)
middleware Middleware
sessionStore store.IStore
@@ -128,3 +160,621 @@ func (ctx *Context) IsStopped() bool {
func (ctx *Context) GetHandlerName() string {
return runtime.FuncForPC(reflect.ValueOf(ctx.middleware[len(ctx.middleware)-1]).Pointer()).Name()
}
+
+/* Request */
+
+// Param returns the string representation of the key's path named parameter's value
+func (ctx *Context) Param(key string) string {
+ return ctx.Params.Get(key)
+}
+
+// ParamInt returns the int representation of the key's path named parameter's value
+func (ctx *Context) ParamInt(key string) (int, error) {
+ val, err := strconv.Atoi(ctx.Param(key))
+ return val, err
+}
+
+// URLParam returns the get parameter from a request , if any
+func (ctx *Context) URLParam(key string) string {
+ return string(ctx.RequestCtx.Request.URI().QueryArgs().Peek(key))
+}
+
+// URLParams returns a map of a list of each url(query) parameter
+func (ctx *Context) URLParams() map[string]string {
+ urlparams := make(map[string]string)
+ ctx.RequestCtx.Request.URI().QueryArgs().VisitAll(func(key, value []byte) {
+ urlparams[string(key)] = string(value)
+ })
+ return urlparams
+}
+
+// URLParamInt returns the get parameter int value from a request , if any
+func (ctx *Context) URLParamInt(key string) (int, error) {
+ return strconv.Atoi(ctx.URLParam(key))
+}
+
+// MethodString returns the HTTP Method
+func (ctx *Context) MethodString() string {
+ return utils.BytesToString(ctx.Method())
+}
+
+// HostString returns the Host of the request( the url as string )
+func (ctx *Context) HostString() string {
+ return utils.BytesToString(ctx.Host())
+}
+
+// VirtualHostname returns the hostname that user registers, host path maybe differs from the real which is HostString, which taken from a net.listener
+func (ctx *Context) VirtualHostname() string {
+ realhost := ctx.HostString()
+ virtualhost := ctx.framework.HTTPServer.VirtualHostname()
+ hostname := strings.Replace(realhost, "127.0.0.1", virtualhost, 1)
+ hostname = strings.Replace(realhost, "localhost", virtualhost, 1)
+ if portIdx := strings.IndexByte(hostname, ':'); portIdx > 0 {
+ hostname = hostname[0:portIdx]
+ }
+ return hostname
+}
+
+// PathString returns the full escaped path as string
+// for unescaped use: ctx.RequestCtx.RequestURI() or RequestPath(escape bool)
+func (ctx *Context) PathString() string {
+ return ctx.RequestPath(true)
+}
+
+// RequestPath returns the requested path
+func (ctx *Context) RequestPath(escape bool) string {
+ if escape {
+ return utils.BytesToString(ctx.RequestCtx.Path())
+ }
+ return utils.BytesToString(ctx.RequestCtx.RequestURI())
+}
+
+// RequestIP gets just the Remote Address from the client.
+func (ctx *Context) RequestIP() string {
+ if ip, _, err := net.SplitHostPort(strings.TrimSpace(ctx.RequestCtx.RemoteAddr().String())); err == nil {
+ return ip
+ }
+ return ""
+}
+
+// RemoteAddr is like RequestIP but it checks for proxy servers also, tries to get the real client's request IP
+func (ctx *Context) RemoteAddr() string {
+ header := string(ctx.RequestCtx.Request.Header.Peek("X-Real-Ip"))
+ realIP := strings.TrimSpace(header)
+ if realIP != "" {
+ return realIP
+ }
+ realIP = string(ctx.RequestCtx.Request.Header.Peek("X-Forwarded-For"))
+ idx := strings.IndexByte(realIP, ',')
+ if idx >= 0 {
+ realIP = realIP[0:idx]
+ }
+ realIP = strings.TrimSpace(realIP)
+ if realIP != "" {
+ return realIP
+ }
+ return ctx.RequestIP()
+
+}
+
+// RequestHeader returns the request header's value
+// accepts one parameter, the key of the header (string)
+// returns string
+func (ctx *Context) RequestHeader(k string) string {
+ return utils.BytesToString(ctx.RequestCtx.Request.Header.Peek(k))
+}
+
+// PostFormValue returns a single value from post request's data
+func (ctx *Context) PostFormValue(name string) string {
+ return string(ctx.RequestCtx.PostArgs().Peek(name))
+}
+
+// PostFormMulti returns a slice of string from post request's data
+func (ctx *Context) PostFormMulti(name string) []string {
+ arrBytes := ctx.PostArgs().PeekMulti(name)
+ arrStr := make([]string, len(arrBytes))
+ for i, v := range arrBytes {
+ arrStr[i] = string(v)
+ }
+ return arrStr
+}
+
+// Subdomain returns the subdomain (string) of this request, if any
+func (ctx *Context) Subdomain() (subdomain string) {
+ host := ctx.HostString()
+ if index := strings.IndexByte(host, '.'); index > 0 {
+ subdomain = host[0:index]
+ }
+
+ return
+}
+
+// URLEncode returns the path encoded as url
+// useful when you want to pass something to a database and be valid to retrieve it via context.Param
+// use it only for special cases, when the default behavior doesn't suits you.
+//
+// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
+/* Credits to Manish Singh @kryptodev for URLEncode */
+func URLEncode(path string) string {
+ if path == "" {
+ return ""
+ }
+ u := fasthttp.AcquireURI()
+ u.SetPath(path)
+ encodedPath := u.String()[8:]
+ fasthttp.ReleaseURI(u)
+ return encodedPath
+}
+
+// ReadJSON reads JSON from request's body
+func (ctx *Context) ReadJSON(jsonObject interface{}) error {
+ data := ctx.RequestCtx.Request.Body()
+
+ decoder := json.NewDecoder(strings.NewReader(string(data)))
+ err := decoder.Decode(jsonObject)
+
+ //err != nil fix by @shiena
+ if err != nil && err != io.EOF {
+ return errReadBody.Format("JSON", err.Error())
+ }
+
+ return nil
+}
+
+// ReadXML reads XML from request's body
+func (ctx *Context) ReadXML(xmlObject interface{}) error {
+ data := ctx.RequestCtx.Request.Body()
+
+ decoder := xml.NewDecoder(strings.NewReader(string(data)))
+ err := decoder.Decode(xmlObject)
+ //err != nil fix by @shiena
+ if err != nil && err != io.EOF {
+ return errReadBody.Format("XML", err.Error())
+ }
+
+ return nil
+}
+
+// ReadForm binds the formObject with the form data
+// it supports any kind of struct
+func (ctx *Context) ReadForm(formObject interface{}) error {
+ reqCtx := ctx.RequestCtx
+ // first check if we have multipart form
+ multipartForm, err := reqCtx.MultipartForm()
+ if err == nil {
+ //we have multipart form
+ return errReadBody.With(formBinder.Decode(multipartForm.Value, formObject))
+ }
+ // if no multipart and post arguments ( means normal form)
+
+ if reqCtx.PostArgs().Len() == 0 && reqCtx.QueryArgs().Len() == 0 {
+ return errReadBody.With(errNoForm.Return())
+ }
+
+ form := make(map[string][]string, reqCtx.PostArgs().Len()+reqCtx.QueryArgs().Len())
+
+ reqCtx.PostArgs().VisitAll(func(k []byte, v []byte) {
+ key := string(k)
+ value := string(v)
+ // for slices
+ if form[key] != nil {
+ form[key] = append(form[key], value)
+ } else {
+ form[key] = []string{value}
+ }
+
+ })
+
+ reqCtx.QueryArgs().VisitAll(func(k []byte, v []byte) {
+ key := string(k)
+ value := string(v)
+ // for slices
+ if form[key] != nil {
+ form[key] = append(form[key], value)
+ } else {
+ form[key] = []string{value}
+ }
+ })
+
+ return errReadBody.With(formBinder.Decode(form, formObject))
+}
+
+/* Response */
+
+// SetContentType sets the response writer's header key 'Content-Type' to a given value(s)
+func (ctx *Context) SetContentType(s string) {
+ ctx.RequestCtx.Response.Header.Set(contentType, s)
+}
+
+// SetHeader write to the response writer's header to a given key the given value(s)
+func (ctx *Context) SetHeader(k string, v string) {
+ ctx.RequestCtx.Response.Header.Set(k, v)
+}
+
+// Redirect redirect sends a redirect response the client
+// accepts 2 parameters string and an optional int
+// first parameter is the url to redirect
+// second parameter is the http status should send, default is 302 (StatusFound), you can set it to 301 (Permant redirect), if that's nessecery
+func (ctx *Context) Redirect(urlToRedirect string, statusHeader ...int) {
+ httpStatus := StatusFound // temporary redirect
+ if statusHeader != nil && len(statusHeader) > 0 && statusHeader[0] > 0 {
+ httpStatus = statusHeader[0]
+ }
+ ctx.RequestCtx.Redirect(urlToRedirect, httpStatus)
+ ctx.StopExecution()
+}
+
+// RedirectTo does the same thing as Redirect but instead of receiving a uri or path it receives a route name
+func (ctx *Context) RedirectTo(routeName string, args ...interface{}) {
+ s := ctx.framework.URL(routeName, args...)
+ if s != "" {
+ ctx.Redirect(s, StatusFound)
+ }
+}
+
+// NotFound emits an error 404 to the client, using the custom http errors
+// if no custom errors provided then it sends the default error message
+func (ctx *Context) NotFound() {
+ ctx.framework.EmitError(StatusNotFound, ctx)
+}
+
+// Panic emits an error 500 to the client, using the custom http errors
+// if no custom errors rpovided then it sends the default error message
+func (ctx *Context) Panic() {
+ ctx.framework.EmitError(StatusInternalServerError, ctx)
+}
+
+// EmitError executes the custom error by the http status code passed to the function
+func (ctx *Context) EmitError(statusCode int) {
+ ctx.framework.EmitError(statusCode, ctx)
+ ctx.StopExecution()
+}
+
+// Write writes a string to the client, something like fmt.Printf but for the web
+func (ctx *Context) Write(format string, a ...interface{}) {
+ //this doesn't work with gzip, so just write the []byte better |ctx.ResponseWriter.WriteString(fmt.Sprintf(format, a...))
+ ctx.RequestCtx.WriteString(fmt.Sprintf(format, a...))
+}
+
+// HTML writes html string with a http status
+func (ctx *Context) HTML(httpStatus int, htmlContents string) {
+ ctx.SetContentType(contentHTML + ctx.framework.rest.CompiledCharset)
+ ctx.RequestCtx.SetStatusCode(httpStatus)
+ ctx.RequestCtx.WriteString(htmlContents)
+}
+
+// Data writes out the raw bytes as binary data.
+func (ctx *Context) Data(status int, v []byte) error {
+ return ctx.framework.rest.Data(ctx.RequestCtx, status, v)
+}
+
+// RenderWithStatus builds up the response from the specified template and bindings.
+// Note: parameter layout has meaning only when using the iris.HTMLTemplate
+func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, layout ...string) error {
+ ctx.SetStatusCode(status)
+ return ctx.framework.templates.Render(ctx, name, binding, layout...)
+}
+
+// Render same as .RenderWithStatus but with status to iris.StatusOK (200)
+func (ctx *Context) Render(name string, binding interface{}, layout ...string) error {
+ return ctx.RenderWithStatus(StatusOK, name, binding, layout...)
+}
+
+// MustRender same as .Render but returns 500 internal server http status (error) if rendering fail
+func (ctx *Context) MustRender(name string, binding interface{}, layout ...string) {
+ if err := ctx.Render(name, binding, layout...); err != nil {
+ ctx.Panic()
+ ctx.framework.Logger.Dangerf("MustRender panics for client with IP: %s On template: %s", ctx.RemoteAddr(), name)
+ }
+}
+
+// TemplateString accepts a template filename, its context data and returns the result of the parsed template (string)
+// if any error returns empty string
+func (ctx *Context) TemplateString(name string, binding interface{}, layout ...string) string {
+ return ctx.framework.TemplateString(name, binding, layout...)
+}
+
+// JSON marshals the given interface object and writes the JSON response.
+func (ctx *Context) JSON(status int, v interface{}) error {
+ return ctx.framework.rest.JSON(ctx.RequestCtx, status, v)
+}
+
+// JSONP marshals the given interface object and writes the JSON response.
+func (ctx *Context) JSONP(status int, callback string, v interface{}) error {
+ return ctx.framework.rest.JSONP(ctx.RequestCtx, status, callback, v)
+}
+
+// Text writes out a string as plain text.
+func (ctx *Context) Text(status int, v string) error {
+ return ctx.framework.rest.Text(ctx.RequestCtx, status, v)
+}
+
+// XML marshals the given interface object and writes the XML response.
+func (ctx *Context) XML(status int, v interface{}) error {
+ return ctx.framework.rest.XML(ctx.RequestCtx, status, v)
+}
+
+// MarkdownString parses the (dynamic) markdown string and returns the converted html string
+func (ctx *Context) MarkdownString(markdownText string) string {
+ return ctx.framework.rest.Markdown([]byte(markdownText))
+}
+
+// Markdown parses and renders to the client a particular (dynamic) markdown string
+// accepts two parameters
+// first is the http status code
+// second is the markdown string
+func (ctx *Context) Markdown(status int, markdown string) {
+ ctx.HTML(status, ctx.MarkdownString(markdown))
+}
+
+// ExecuteTemplate executes a simple html template, you can use that if you already have the cached templates
+// the recommended way to render is to use iris.Templates("./templates/path/*.html") and ctx.RenderFile("filename.html",struct{})
+// accepts 2 parameters
+// the first parameter is the template (*template.Template)
+// the second parameter is the page context (interfac{})
+// returns an error if any errors occurs while executing this template
+func (ctx *Context) ExecuteTemplate(tmpl *template.Template, pageContext interface{}) error {
+ ctx.RequestCtx.SetContentType(contentHTML + ctx.framework.rest.CompiledCharset)
+ return errTemplateExecute.With(tmpl.Execute(ctx.RequestCtx.Response.BodyWriter(), pageContext))
+}
+
+// ServeContent serves content, headers are autoset
+// receives three parameters, it's low-level function, instead you can use .ServeFile(string)
+//
+// You can define your own "Content-Type" header also, after this function call
+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)) {
+ ctx.RequestCtx.Response.Header.Del(contentType)
+ ctx.RequestCtx.Response.Header.Del(contentLength)
+ ctx.RequestCtx.SetStatusCode(StatusNotModified)
+ return nil
+ }
+
+ ctx.RequestCtx.Response.Header.Set(contentType, utils.TypeByExtension(filename))
+ ctx.RequestCtx.Response.Header.Set(lastModified, modtime.UTC().Format(config.TimeFormat))
+ ctx.RequestCtx.SetStatusCode(StatusOK)
+ var out io.Writer
+ if gzipCompression {
+ ctx.RequestCtx.Response.Header.Add("Content-Encoding", "gzip")
+ gzipWriter := gzipWriterPool.Get().(*gzip.Writer)
+ gzipWriter.Reset(ctx.RequestCtx.Response.BodyWriter())
+ defer gzipWriter.Close()
+ defer gzipWriterPool.Put(gzipWriter)
+ out = gzipWriter
+ } else {
+ out = ctx.RequestCtx.Response.BodyWriter()
+
+ }
+ _, err := io.Copy(out, content)
+ return errServeContent.With(err)
+}
+
+// ServeFile serves a view file, to send a file ( zip for example) to the client you should use the SendFile(serverfilename,clientfilename)
+// receives two parameters
+// filename/path (string)
+// gzipCompression (bool)
+//
+// You can define your own "Content-Type" header also, after this function call
+func (ctx *Context) ServeFile(filename string, gzipCompression bool) error {
+ f, err := os.Open(filename)
+ if err != nil {
+ return fmt.Errorf("%d", 404)
+ }
+ defer f.Close()
+ fi, _ := f.Stat()
+ if fi.IsDir() {
+ filename = path.Join(filename, "index.html")
+ f, err = os.Open(filename)
+ if err != nil {
+ return fmt.Errorf("%d", 404)
+ }
+ fi, _ = f.Stat()
+ }
+ return ctx.ServeContent(f, fi.Name(), fi.ModTime(), gzipCompression)
+}
+
+// SendFile sends file for force-download to the client
+//
+// You can define your own "Content-Type" header also, after this function call
+// for example: ctx.Response.Header.Set("Content-Type","thecontent/type")
+func (ctx *Context) SendFile(filename string, destinationName string) error {
+ err := ctx.ServeFile(filename, false)
+ if err != nil {
+ return err
+ }
+
+ ctx.RequestCtx.Response.Header.Set(contentDisposition, "attachment;filename="+destinationName)
+ return nil
+}
+
+// Stream same as StreamWriter
+func (ctx *Context) Stream(cb func(writer *bufio.Writer)) {
+ ctx.StreamWriter(cb)
+}
+
+// StreamWriter registers the given stream writer for populating
+// response body.
+//
+//
+// This function may be used in the following cases:
+//
+// * if response body is too big (more than 10MB).
+// * if response body is streamed from slow external sources.
+// * if response body must be streamed to the client in chunks.
+// (aka `http server push`).
+func (ctx *Context) StreamWriter(cb func(writer *bufio.Writer)) {
+ ctx.RequestCtx.SetBodyStreamWriter(cb)
+}
+
+// StreamReader sets response body stream and, optionally body size.
+//
+// If bodySize is >= 0, then the bodyStream must provide exactly bodySize bytes
+// before returning io.EOF.
+//
+// If bodySize < 0, then bodyStream is read until io.EOF.
+//
+// bodyStream.Close() is called after finishing reading all body data
+// if it implements io.Closer.
+//
+// See also StreamReader.
+func (ctx *Context) StreamReader(bodyStream io.Reader, bodySize int) {
+ ctx.RequestCtx.Response.SetBodyStream(bodyStream, bodySize)
+}
+
+/* Storage */
+
+// Get returns the user's value from a key
+// if doesn't exists returns nil
+func (ctx *Context) Get(key string) interface{} {
+ return ctx.RequestCtx.UserValue(key)
+}
+
+// GetFmt returns a value which has this format: func(format string, args ...interface{}) string
+// if doesn't exists returns nil
+func (ctx *Context) GetFmt(key string) func(format string, args ...interface{}) string {
+ if v, ok := ctx.Get(key).(func(format string, args ...interface{}) string); ok {
+ return v
+ }
+ return func(format string, args ...interface{}) string { return "" }
+
+}
+
+// GetString same as Get but returns the value as string
+// if nothing founds returns empty string ""
+func (ctx *Context) GetString(key string) string {
+ if v, ok := ctx.Get(key).(string); ok {
+ return v
+ }
+
+ return ""
+}
+
+// GetInt same as Get but returns the value as int
+// if nothing founds returns -1
+func (ctx *Context) GetInt(key string) int {
+ if v, ok := ctx.Get(key).(int); ok {
+ return v
+ }
+
+ return -1
+}
+
+// Set sets a value to a key in the values map
+func (ctx *Context) Set(key string, value interface{}) {
+ ctx.RequestCtx.SetUserValue(key, value)
+}
+
+// GetCookie returns cookie's value by it's name
+// returns empty string if nothing was found
+func (ctx *Context) GetCookie(name string) (val string) {
+ bcookie := ctx.RequestCtx.Request.Header.Cookie(name)
+ if bcookie != nil {
+ val = string(bcookie)
+ }
+ return
+}
+
+// SetCookie adds a cookie
+func (ctx *Context) SetCookie(cookie *fasthttp.Cookie) {
+ ctx.RequestCtx.Response.Header.SetCookie(cookie)
+}
+
+// SetCookieKV adds a cookie, receives just a key(string) and a value(string)
+func (ctx *Context) SetCookieKV(key, value string) {
+ c := fasthttp.AcquireCookie() // &fasthttp.Cookie{}
+ c.SetKey(key)
+ c.SetValue(value)
+ c.SetHTTPOnly(true)
+ c.SetExpire(time.Now().Add(time.Duration(120) * time.Minute))
+ ctx.SetCookie(c)
+ fasthttp.ReleaseCookie(c)
+}
+
+// RemoveCookie deletes a cookie by it's name/key
+func (ctx *Context) RemoveCookie(name string) {
+ cookie := fasthttp.AcquireCookie()
+ cookie.SetKey(name)
+ cookie.SetValue("")
+ cookie.SetPath("/")
+ cookie.SetHTTPOnly(true)
+ exp := time.Now().Add(-time.Duration(1) * time.Minute) //RFC says 1 second, but make sure 1 minute because we are using fasthttp
+ cookie.SetExpire(exp)
+ ctx.Response.Header.SetCookie(cookie)
+ fasthttp.ReleaseCookie(cookie)
+}
+
+// GetFlash get a flash message by it's key
+// after this action the messages is removed
+// returns string, if the cookie doesn't exists the string is empty
+func (ctx *Context) GetFlash(key string) string {
+ val, err := ctx.GetFlashBytes(key)
+ if err != nil {
+ return ""
+ }
+ return string(val)
+}
+
+// GetFlashBytes get a flash message by it's key
+// after this action the messages is removed
+// returns []byte along with an error if the cookie doesn't exists or decode fails
+func (ctx *Context) GetFlashBytes(key string) (value []byte, err error) {
+ cookieValue := string(ctx.RequestCtx.Request.Header.Cookie(key))
+ if cookieValue == "" {
+ err = errFlashNotFound.Return()
+ } else {
+ value, err = base64.URLEncoding.DecodeString(cookieValue)
+ //remove the message
+ ctx.RemoveCookie(key)
+ //it should'b be removed until the next reload, so we don't do that: ctx.Request.Header.SetCookie(key, "")
+ }
+ return
+}
+
+// SetFlash sets a flash message, accepts 2 parameters the key(string) and the value(string)
+func (ctx *Context) SetFlash(key string, value string) {
+ ctx.SetFlashBytes(key, utils.StringToBytes(value))
+}
+
+// SetFlashBytes sets a flash message, accepts 2 parameters the key(string) and the value([]byte)
+func (ctx *Context) SetFlashBytes(key string, value []byte) {
+ c := fasthttp.AcquireCookie()
+ c.SetKey(key)
+ c.SetValue(base64.URLEncoding.EncodeToString(value))
+ c.SetPath("/")
+ c.SetHTTPOnly(true)
+ ctx.RequestCtx.Response.Header.SetCookie(c)
+ fasthttp.ReleaseCookie(c)
+}
+
+// Session returns the current session store, returns nil if provider is ""
+func (ctx *Context) Session() store.IStore {
+ if ctx.framework.sessions == nil || ctx.framework.Config.Sessions.Provider == "" { //the second check can be changed on runtime, users are able to turn off the sessions by setting provider to ""
+ return nil
+ }
+
+ if ctx.sessionStore == nil {
+ ctx.sessionStore = ctx.framework.sessions.Start(ctx)
+ }
+ return ctx.sessionStore
+}
+
+// SessionDestroy destroys the whole session, calls the provider's destroy and remove the cookie
+func (ctx *Context) SessionDestroy() {
+ if ctx.framework.sessions != nil {
+ if store := ctx.Session(); store != nil {
+ ctx.framework.sessions.Destroy(ctx)
+ }
+ }
+
+}
+
+// SendMail sends a mail to recipients
+// the body can be html also
+func (ctx *Context) SendMail(subject string, body string, to ...string) error {
+ return ctx.framework.SendMail(subject, body, to...)
+}
+
+// Log logs to the iris defined logger
+func (ctx *Context) Log(format string, a ...interface{}) {
+ ctx.framework.Logger.Printf(format, a...)
+}
diff --git a/context/context.go b/context/context.go
index eba14304..85670df4 100644
--- a/context/context.go
+++ b/context/context.go
@@ -18,7 +18,8 @@ type (
IContextBinder
IContextRequest
IContextResponse
-
+ SendMail(string, string, ...string) error
+ Log(string, ...interface{})
Reset(*fasthttp.RequestCtx)
GetRequestCtx() *fasthttp.RequestCtx
Clone() IContext
@@ -39,17 +40,18 @@ type (
// IContextRenderer is part of the IContext
IContextRenderer interface {
Write(string, ...interface{})
- WriteHTML(int, string)
+ HTML(int, string)
// Data writes out the raw bytes as binary data.
Data(status int, v []byte) error
- // HTML builds up the response from the specified template and bindings.
- HTML(status int, name string, binding interface{}, layout ...string) error
- // Render same as .HTML but with status to iris.StatusOK (200)
+ // RenderWithStatus builds up the response from the specified template and bindings.
+ RenderWithStatus(status int, name string, binding interface{}, layout ...string) error
+ // Render same as .RenderWithStatus but with status to iris.StatusOK (200)
Render(name string, binding interface{}, layout ...string) error
// MustRender same as .Render but returns 500 internal server http status (error) if rendering fail
MustRender(name string, binding interface{}, layout ...string)
- // RenderString accepts a template filename, its context data and returns the result of the parsed template (string)
- RenderString(name string, binding interface{}, layout ...string) (result string, err error)
+ // TemplateString accepts a template filename, its context data and returns the result of the parsed template (string)
+ // if any error returns empty string
+ TemplateString(name string, binding interface{}, layout ...string) string
// MarkdownString parses the (dynamic) markdown string and returns the converted html string
MarkdownString(markdown string) string
// Markdown parses and renders to the client a particular (dynamic) markdown string
@@ -86,10 +88,13 @@ type (
HostString() string
Subdomain() string
PathString() string
+ RequestPath(bool) string
RequestIP() string
RemoteAddr() string
RequestHeader(k string) string
PostFormValue(string) string
+ // PostFormMulti returns a slice of string from post request's data
+ PostFormMulti(string) []string
}
// IContextResponse is part of the IContext
diff --git a/context_renderer.go b/context_renderer.go
deleted file mode 100644
index 00e92086..00000000
--- a/context_renderer.go
+++ /dev/null
@@ -1,203 +0,0 @@
-package iris
-
-import (
- "bufio"
- "fmt"
- "html/template"
- "io"
- "os"
- "path"
- "time"
-
- "github.com/kataras/iris/utils"
- "github.com/klauspost/compress/gzip"
-)
-
-// Write writes a string via the context's ResponseWriter
-func (ctx *Context) Write(format string, a ...interface{}) {
- //this doesn't work with gzip, so just write the []byte better |ctx.ResponseWriter.WriteString(fmt.Sprintf(format, a...))
- ctx.RequestCtx.WriteString(fmt.Sprintf(format, a...))
-}
-
-// WriteHTML writes html string with a http status
-func (ctx *Context) WriteHTML(httpStatus int, htmlContents string) {
- ctx.SetContentType(ContentHTML + ctx.station.rest.CompiledCharset)
- ctx.RequestCtx.SetStatusCode(httpStatus)
- ctx.RequestCtx.WriteString(htmlContents)
-}
-
-// Data writes out the raw bytes as binary data.
-func (ctx *Context) Data(status int, v []byte) error {
- return ctx.station.rest.Data(ctx.RequestCtx, status, v)
-}
-
-// HTML builds up the response from the specified template and bindings.
-// Note: parameter layout has meaning only when using the iris.HTMLTemplate
-func (ctx *Context) HTML(status int, name string, binding interface{}, layout ...string) error {
- ctx.SetStatusCode(status)
- return ctx.station.templates.Render(ctx, name, binding, layout...)
-}
-
-// Render same as .HTML but with status to iris.StatusOK (200)
-func (ctx *Context) Render(name string, binding interface{}, layout ...string) error {
- return ctx.HTML(StatusOK, name, binding, layout...)
-}
-
-// MustRender same as .Render but returns 500 internal server http status (error) if rendering fail
-func (ctx *Context) MustRender(name string, binding interface{}, layout ...string) {
- if err := ctx.Render(name, binding, layout...); err != nil {
- ctx.Panic()
- }
-}
-
-// RenderString accepts a template filename, its context data and returns the result of the parsed template (string)
-func (ctx *Context) RenderString(name string, binding interface{}, layout ...string) (result string, err error) {
- return ctx.station.templates.RenderString(name, binding, layout...)
-}
-
-// JSON marshals the given interface object and writes the JSON response.
-func (ctx *Context) JSON(status int, v interface{}) error {
- return ctx.station.rest.JSON(ctx.RequestCtx, status, v)
-}
-
-// JSONP marshals the given interface object and writes the JSON response.
-func (ctx *Context) JSONP(status int, callback string, v interface{}) error {
- return ctx.station.rest.JSONP(ctx.RequestCtx, status, callback, v)
-}
-
-// Text writes out a string as plain text.
-func (ctx *Context) Text(status int, v string) error {
- return ctx.station.rest.Text(ctx.RequestCtx, status, v)
-}
-
-// XML marshals the given interface object and writes the XML response.
-func (ctx *Context) XML(status int, v interface{}) error {
- return ctx.station.rest.XML(ctx.RequestCtx, status, v)
-}
-
-// MarkdownString parses the (dynamic) markdown string and returns the converted html string
-func (ctx *Context) MarkdownString(markdown string) string {
- return ctx.station.rest.Markdown([]byte(markdown))
-}
-
-// Markdown parses and renders to the client a particular (dynamic) markdown string
-// accepts two parameters
-// first is the http status code
-// second is the markdown string
-func (ctx *Context) Markdown(status int, markdown string) {
- ctx.WriteHTML(status, ctx.MarkdownString(markdown))
-}
-
-// ExecuteTemplate executes a simple html template, you can use that if you already have the cached templates
-// the recommended way to render is to use iris.Templates("./templates/path/*.html") and ctx.RenderFile("filename.html",struct{})
-// accepts 2 parameters
-// the first parameter is the template (*template.Template)
-// the second parameter is the page context (interfac{})
-// returns an error if any errors occurs while executing this template
-func (ctx *Context) ExecuteTemplate(tmpl *template.Template, pageContext interface{}) error {
- ctx.RequestCtx.SetContentType(ContentHTML + ctx.station.rest.CompiledCharset)
- return ErrTemplateExecute.With(tmpl.Execute(ctx.RequestCtx.Response.BodyWriter(), pageContext))
-}
-
-// ServeContent serves content, headers are autoset
-// receives three parameters, it's low-level function, instead you can use .ServeFile(string)
-//
-// You can define your own "Content-Type" header also, after this function call
-func (ctx *Context) ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error {
- if t, err := time.Parse(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)
- return nil
- }
-
- ctx.RequestCtx.Response.Header.Set(ContentType, utils.TypeByExtension(filename))
- ctx.RequestCtx.Response.Header.Set(LastModified, modtime.UTC().Format(TimeFormat))
- ctx.RequestCtx.SetStatusCode(StatusOK)
- var out io.Writer
- if gzipCompression {
- ctx.RequestCtx.Response.Header.Add("Content-Encoding", "gzip")
- gzipWriter := ctx.station.gzipWriterPool.Get().(*gzip.Writer)
- gzipWriter.Reset(ctx.RequestCtx.Response.BodyWriter())
- defer gzipWriter.Close()
- defer ctx.station.gzipWriterPool.Put(gzipWriter)
- out = gzipWriter
- } else {
- out = ctx.RequestCtx.Response.BodyWriter()
-
- }
- _, err := io.Copy(out, content)
- return ErrServeContent.With(err)
-}
-
-// ServeFile serves a view file, to send a file ( zip for example) to the client you should use the SendFile(serverfilename,clientfilename)
-// receives two parameters
-// filename/path (string)
-// gzipCompression (bool)
-//
-// You can define your own "Content-Type" header also, after this function call
-func (ctx *Context) ServeFile(filename string, gzipCompression bool) error {
- f, err := os.Open(filename)
- if err != nil {
- return fmt.Errorf("%d", 404)
- }
- defer f.Close()
- fi, _ := f.Stat()
- if fi.IsDir() {
- filename = path.Join(filename, "index.html")
- f, err = os.Open(filename)
- if err != nil {
- return fmt.Errorf("%d", 404)
- }
- fi, _ = f.Stat()
- }
- return ctx.ServeContent(f, fi.Name(), fi.ModTime(), gzipCompression)
-}
-
-// SendFile sends file for force-download to the client
-//
-// You can define your own "Content-Type" header also, after this function call
-// for example: ctx.Response.Header.Set("Content-Type","thecontent/type")
-func (ctx *Context) SendFile(filename string, destinationName string) error {
- err := ctx.ServeFile(filename, false)
- if err != nil {
- return err
- }
-
- ctx.RequestCtx.Response.Header.Set(ContentDisposition, "attachment;filename="+destinationName)
- return nil
-}
-
-// Stream same as StreamWriter
-func (ctx *Context) Stream(cb func(writer *bufio.Writer)) {
- ctx.StreamWriter(cb)
-}
-
-// StreamWriter registers the given stream writer for populating
-// response body.
-//
-//
-// This function may be used in the following cases:
-//
-// * if response body is too big (more than 10MB).
-// * if response body is streamed from slow external sources.
-// * if response body must be streamed to the client in chunks.
-// (aka `http server push`).
-func (ctx *Context) StreamWriter(cb func(writer *bufio.Writer)) {
- ctx.RequestCtx.SetBodyStreamWriter(cb)
-}
-
-// StreamReader sets response body stream and, optionally body size.
-//
-// If bodySize is >= 0, then the bodyStream must provide exactly bodySize bytes
-// before returning io.EOF.
-//
-// If bodySize < 0, then bodyStream is read until io.EOF.
-//
-// bodyStream.Close() is called after finishing reading all body data
-// if it implements io.Closer.
-//
-// See also StreamReader.
-func (ctx *Context) StreamReader(bodyStream io.Reader, bodySize int) {
- ctx.RequestCtx.Response.SetBodyStream(bodyStream, bodySize)
-}
diff --git a/context_request.go b/context_request.go
deleted file mode 100644
index 0aae9480..00000000
--- a/context_request.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package iris
-
-import (
- "net"
- "strconv"
- "strings"
-
- "github.com/kataras/iris/bindings"
- "github.com/kataras/iris/utils"
- "github.com/valyala/fasthttp"
-)
-
-// Param returns the string representation of the key's path named parameter's value
-func (ctx *Context) Param(key string) string {
- return ctx.Params.Get(key)
-}
-
-// ParamInt returns the int representation of the key's path named parameter's value
-func (ctx *Context) ParamInt(key string) (int, error) {
- val, err := strconv.Atoi(ctx.Param(key))
- return val, err
-}
-
-// URLParam returns the get parameter from a request , if any
-func (ctx *Context) URLParam(key string) string {
- return string(ctx.RequestCtx.Request.URI().QueryArgs().Peek(key))
-}
-
-// URLParams returns a map of a list of each url(query) parameter
-func (ctx *Context) URLParams() map[string]string {
- urlparams := make(map[string]string)
- ctx.RequestCtx.Request.URI().QueryArgs().VisitAll(func(key, value []byte) {
- urlparams[string(key)] = string(value)
- })
- return urlparams
-}
-
-// URLParamInt returns the get parameter int value from a request , if any
-func (ctx *Context) URLParamInt(key string) (int, error) {
- return strconv.Atoi(ctx.URLParam(key))
-}
-
-// MethodString returns the HTTP Method
-func (ctx *Context) MethodString() string {
- return utils.BytesToString(ctx.Method())
-}
-
-// HostString returns the Host of the request( the url as string )
-func (ctx *Context) HostString() string {
- return utils.BytesToString(ctx.Host())
-}
-
-// PathString returns the full path as string
-func (ctx *Context) PathString() string {
- return utils.BytesToString(ctx.Path())
-}
-
-// RequestIP gets just the Remote Address from the client.
-func (ctx *Context) RequestIP() string {
- if ip, _, err := net.SplitHostPort(strings.TrimSpace(ctx.RequestCtx.RemoteAddr().String())); err == nil {
- return ip
- }
- return ""
-}
-
-// RemoteAddr is like RequestIP but it checks for proxy servers also, tries to get the real client's request IP
-func (ctx *Context) RemoteAddr() string {
- header := string(ctx.RequestCtx.Request.Header.Peek("X-Real-Ip"))
- realIP := strings.TrimSpace(header)
- if realIP != "" {
- return realIP
- }
- realIP = string(ctx.RequestCtx.Request.Header.Peek("X-Forwarded-For"))
- idx := strings.IndexByte(realIP, ',')
- if idx >= 0 {
- realIP = realIP[0:idx]
- }
- realIP = strings.TrimSpace(realIP)
- if realIP != "" {
- return realIP
- }
- return ctx.RequestIP()
-
-}
-
-// RequestHeader returns the request header's value
-// accepts one parameter, the key of the header (string)
-// returns string
-func (ctx *Context) RequestHeader(k string) string {
- return utils.BytesToString(ctx.RequestCtx.Request.Header.Peek(k))
-}
-
-// PostFormValue returns a single value from post request's data
-func (ctx *Context) PostFormValue(name string) string {
- return string(ctx.RequestCtx.PostArgs().Peek(name))
-}
-
-// Subdomain returns the subdomain (string) of this request, if any
-func (ctx *Context) Subdomain() (subdomain string) {
- host := ctx.HostString()
- if index := strings.IndexByte(host, '.'); index > 0 {
- subdomain = host[0:index]
- }
-
- return
-}
-
-// URLEncode returns the path encoded as url
-// useful when you want to pass something to a database and be valid to retrieve it via context.Param
-// use it only for special cases, when the default behavior doesn't suits you.
-//
-// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
-/* Credits to Manish Singh @kryptodev for URLEncode */
-func URLEncode(path string) string {
- if path == "" {
- return ""
- }
- u := fasthttp.AcquireURI()
- u.SetPath(path)
- encodedPath := u.String()[8:]
- fasthttp.ReleaseURI(u)
- return encodedPath
-}
-
-// ReadJSON reads JSON from request's body
-func (ctx *Context) ReadJSON(jsonObject interface{}) error {
- return bindings.BindJSON(ctx, jsonObject)
-}
-
-// ReadXML reads XML from request's body
-func (ctx *Context) ReadXML(xmlObject interface{}) error {
- return bindings.BindXML(ctx, xmlObject)
-}
-
-// ReadForm binds the formObject with the form data
-// it supports any kind of struct
-func (ctx *Context) ReadForm(formObject interface{}) error {
- return bindings.BindForm(ctx, formObject)
-}
diff --git a/context_response.go b/context_response.go
deleted file mode 100644
index 54e70634..00000000
--- a/context_response.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package iris
-
-// SetContentType sets the response writer's header key 'Content-Type' to a given value(s)
-func (ctx *Context) SetContentType(s string) {
- ctx.RequestCtx.Response.Header.Set(ContentType, s)
-}
-
-// SetHeader write to the response writer's header to a given key the given value(s)
-func (ctx *Context) SetHeader(k string, v string) {
- ctx.RequestCtx.Response.Header.Set(k, v)
-}
-
-// Redirect redirect sends a redirect response the client
-// accepts 2 parameters string and an optional int
-// first parameter is the url to redirect
-// second parameter is the http status should send, default is 302 (StatusFound), you can set it to 301 (Permant redirect), if that's nessecery
-func (ctx *Context) Redirect(urlToRedirect string, statusHeader ...int) {
- httpStatus := StatusFound // temporary redirect
- if statusHeader != nil && len(statusHeader) > 0 && statusHeader[0] > 0 {
- httpStatus = statusHeader[0]
- }
- ctx.RequestCtx.Redirect(urlToRedirect, httpStatus)
- ctx.StopExecution()
-}
-
-// RedirectTo does the same thing as Redirect but instead of receiving a uri or path it receives a route name
-func (ctx *Context) RedirectTo(routeName string, args ...interface{}) {
- s := ctx.station.RouteByName(routeName).ParseURI(args...)
- if s != "" {
- ctx.Redirect(s, StatusFound)
- }
-}
-
-// Error handling
-
-// NotFound emits an error 404 to the client, using the custom http errors
-// if no custom errors provided then it sends the default http.NotFound
-func (ctx *Context) NotFound() {
- ctx.StopExecution()
- ctx.station.EmitError(404, ctx)
-}
-
-// Panic stops the executions of the context and returns the registed panic handler
-// or if not, the default which is 500 http status to the client
-//
-// This function is useful when you use the recovery middleware, which is auto-executing the (custom, registed) 500 internal server error.
-func (ctx *Context) Panic() {
- ctx.StopExecution()
- ctx.station.EmitError(500, ctx)
-}
-
-// EmitError executes the custom error by the http status code passed to the function
-func (ctx *Context) EmitError(statusCode int) {
- ctx.station.EmitError(statusCode, ctx)
-}
diff --git a/context_storage.go b/context_storage.go
deleted file mode 100644
index 4cf394a2..00000000
--- a/context_storage.go
+++ /dev/null
@@ -1,157 +0,0 @@
-package iris
-
-import (
- "encoding/base64"
- "time"
-
- "github.com/kataras/iris/sessions/store"
- "github.com/kataras/iris/utils"
- "github.com/valyala/fasthttp"
-)
-
-// After v2.2.3 Get/GetFmt/GetString/GetInt/Set are all return values from the RequestCtx.userValues they are reseting on each connection.
-
-// Get returns the user's value from a key
-// if doesn't exists returns nil
-func (ctx *Context) Get(key string) interface{} {
- return ctx.RequestCtx.UserValue(key)
-}
-
-// GetFmt returns a value which has this format: func(format string, args ...interface{}) string
-// if doesn't exists returns nil
-func (ctx *Context) GetFmt(key string) func(format string, args ...interface{}) string {
- if v, ok := ctx.Get(key).(func(format string, args ...interface{}) string); ok {
- return v
- }
- return func(format string, args ...interface{}) string { return "" }
-
-}
-
-// GetString same as Get but returns the value as string
-// if nothing founds returns empty string ""
-func (ctx *Context) GetString(key string) string {
- if v, ok := ctx.Get(key).(string); ok {
- return v
- }
-
- return ""
-}
-
-// GetInt same as Get but returns the value as int
-// if nothing founds returns -1
-func (ctx *Context) GetInt(key string) int {
- if v, ok := ctx.Get(key).(int); ok {
- return v
- }
-
- return -1
-}
-
-// Set sets a value to a key in the values map
-func (ctx *Context) Set(key string, value interface{}) {
- ctx.RequestCtx.SetUserValue(key, value)
-}
-
-// GetCookie returns cookie's value by it's name
-// returns empty string if nothing was found
-func (ctx *Context) GetCookie(name string) (val string) {
- bcookie := ctx.RequestCtx.Request.Header.Cookie(name)
- if bcookie != nil {
- val = string(bcookie)
- }
- return
-}
-
-// SetCookie adds a cookie
-func (ctx *Context) SetCookie(cookie *fasthttp.Cookie) {
- ctx.RequestCtx.Response.Header.SetCookie(cookie)
-}
-
-// SetCookieKV adds a cookie, receives just a key(string) and a value(string)
-func (ctx *Context) SetCookieKV(key, value string) {
- c := fasthttp.AcquireCookie() // &fasthttp.Cookie{}
- c.SetKey(key)
- c.SetValue(value)
- c.SetHTTPOnly(true)
- c.SetExpire(time.Now().Add(time.Duration(120) * time.Minute))
- ctx.SetCookie(c)
- fasthttp.ReleaseCookie(c)
-}
-
-// RemoveCookie deletes a cookie by it's name/key
-func (ctx *Context) RemoveCookie(name string) {
- cookie := fasthttp.AcquireCookie()
- cookie.SetKey(name)
- cookie.SetValue("")
- cookie.SetPath("/")
- cookie.SetHTTPOnly(true)
- exp := time.Now().Add(-time.Duration(1) * time.Minute) //RFC says 1 second, but make sure 1 minute because we are using fasthttp
- cookie.SetExpire(exp)
- ctx.Response.Header.SetCookie(cookie)
- fasthttp.ReleaseCookie(cookie)
-}
-
-// GetFlash get a flash message by it's key
-// after this action the messages is removed
-// returns string, if the cookie doesn't exists the string is empty
-func (ctx *Context) GetFlash(key string) string {
- val, err := ctx.GetFlashBytes(key)
- if err != nil {
- return ""
- }
- return string(val)
-}
-
-// GetFlashBytes get a flash message by it's key
-// after this action the messages is removed
-// returns []byte along with an error if the cookie doesn't exists or decode fails
-func (ctx *Context) GetFlashBytes(key string) (value []byte, err error) {
- cookieValue := string(ctx.RequestCtx.Request.Header.Cookie(key))
- if cookieValue == "" {
- err = ErrFlashNotFound.Return()
- } else {
- value, err = base64.URLEncoding.DecodeString(cookieValue)
- //remove the message
- ctx.RemoveCookie(key)
- //it should'b be removed until the next reload, so we don't do that: ctx.Request.Header.SetCookie(key, "")
- }
- return
-}
-
-// SetFlash sets a flash message, accepts 2 parameters the key(string) and the value(string)
-func (ctx *Context) SetFlash(key string, value string) {
- ctx.SetFlashBytes(key, utils.StringToBytes(value))
-}
-
-// SetFlashBytes sets a flash message, accepts 2 parameters the key(string) and the value([]byte)
-func (ctx *Context) SetFlashBytes(key string, value []byte) {
- c := fasthttp.AcquireCookie()
- c.SetKey(key)
- c.SetValue(base64.URLEncoding.EncodeToString(value))
- c.SetPath("/")
- c.SetHTTPOnly(true)
- ctx.RequestCtx.Response.Header.SetCookie(c)
- fasthttp.ReleaseCookie(c)
-}
-
-// Session returns the current session store, returns nil if provider is ""
-func (ctx *Context) Session() store.IStore {
- if ctx.station.sessionManager == nil || ctx.station.config.Sessions.Provider == "" { //the second check can be changed on runtime, users are able to turn off the sessions by setting provider to ""
- return nil
- }
-
- if ctx.sessionStore == nil {
- ctx.sessionStore = ctx.station.sessionManager.Start(ctx)
- }
- return ctx.sessionStore
-}
-
-// SessionDestroy destroys the whole session, calls the provider's destroy and remove the cookie
-func (ctx *Context) SessionDestroy() {
- if ctx.station.sessionManager != nil {
- if store := ctx.Session(); store != nil {
- ctx.station.sessionManager.Destroy(ctx)
- }
- }
-
-}
diff --git a/errors.go b/errors.go
deleted file mode 100644
index 31c9779c..00000000
--- a/errors.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package iris
-
-import "github.com/kataras/iris/errors"
-
-var (
- // Router, Party & Handler
-
- // ErrHandler returns na error with message: 'Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)
- // It seems to be a +type Points to: +pointer.'
- ErrHandler = errors.New("Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)\n It seems to be a %T Points to: %v.")
- // ErrHandleAnnotated returns an error with message: 'HandleAnnotated parse: +specific error(s)'
- ErrHandleAnnotated = errors.New("HandleAnnotated parse: %s")
- // ErrControllerContextNotFound returns an error with message: 'Context *iris.Context could not be found, the Controller won't be registed.'
- ErrControllerContextNotFound = errors.New("Context *iris.Context could not be found, the Controller won't be registed.")
- // ErrDirectoryFileNotFound returns an error with message: 'Directory or file %s couldn't found. Trace: +error trace'
- ErrDirectoryFileNotFound = errors.New("Directory or file %s couldn't found. Trace: %s")
- // ErrRenderRouteNotFound returns an error with message 'Route with name +route_name not found', used inside 'url' template func
- ErrRenderRouteNotFound = errors.New("Route with name %s not found")
-
- // Plugin
-
- // ErrPluginAlreadyExists returns an error with message: 'Cannot activate the same plugin again, plugin '+plugin name[+plugin description]' is already exists'
- ErrPluginAlreadyExists = errors.New("Cannot use the same plugin again, '%s[%s]' is already exists")
- // ErrPluginActivate returns an error with message: 'While trying to activate plugin '+plugin name'. Trace: +specific error'
- ErrPluginActivate = errors.New("While trying to activate plugin '%s'. Trace: %s")
- // ErrPluginRemoveNoPlugins returns an error with message: 'No plugins are registed yet, you cannot remove a plugin from an empty list!'
- ErrPluginRemoveNoPlugins = errors.New("No plugins are registed yet, you cannot remove a plugin from an empty list!")
- // ErrPluginRemoveEmptyName returns an error with message: 'Plugin with an empty name cannot be removed'
- ErrPluginRemoveEmptyName = errors.New("Plugin with an empty name cannot be removed")
- // ErrPluginRemoveNotFound returns an error with message: 'Cannot remove a plugin which doesn't exists'
- ErrPluginRemoveNotFound = errors.New("Cannot remove a plugin which doesn't exists")
- // Context other
-
- // ErrServeContent returns an error with message: 'While trying to serve content to the client. Trace +specific error'
- ErrServeContent = errors.New("While trying to serve content to the client. Trace %s")
-
- // ErrTemplateExecute returns an error with message:'Unable to execute a template. Trace: +specific error'
- ErrTemplateExecute = errors.New("Unable to execute a template. Trace: %s")
-
- // ErrFlashNotFound returns an error with message: 'Unable to get flash message. Trace: Cookie does not exists'
- ErrFlashNotFound = errors.New("Unable to get flash message. Trace: Cookie does not exists")
- // ErrSessionNil returns an error with message: 'Unable to set session, Config().Session.Provider is nil, please refer to the docs!'
- ErrSessionNil = errors.New("Unable to set session, Config().Session.Provider is nil, please refer to the docs!")
-)
diff --git a/errors/error.go b/errors/error.go
index c170ccac..5c3fef66 100644
--- a/errors/error.go
+++ b/errors/error.go
@@ -19,7 +19,7 @@ func (e *Error) Error() string {
// Format returns a formatted new error based on the arguments
func (e *Error) Format(args ...interface{}) error {
- return fmt.Errorf(e.message, args)
+ return fmt.Errorf(e.message, args...)
}
// With does the same thing as Format but it receives an error type which if it's nil it returns a nil error
@@ -33,7 +33,7 @@ func (e *Error) With(err error) error {
// Return returns the actual error as it is
func (e *Error) Return() error {
- return fmt.Errorf(e.message)
+ return e.Format()
}
// Panic output the message and after panics
diff --git a/graceful/graceful.go b/graceful/graceful.go
index 60ca19a5..1c60ecce 100644
--- a/graceful/graceful.go
+++ b/graceful/graceful.go
@@ -11,7 +11,6 @@ import (
"github.com/kataras/iris"
"github.com/kataras/iris/config"
"github.com/kataras/iris/logger"
- "github.com/kataras/iris/server"
"golang.org/x/net/netutil"
)
@@ -19,8 +18,8 @@ import (
// It may be used directly in the same way as iris.Server, or may
// be constructed with the global functions in this package.
type Server struct {
- *server.Server
- station *iris.Iris
+ *iris.Server
+ station *iris.Framework
// Timeout is the duration to allow outstanding requests to survive
// before forcefully terminating them.
Timeout time.Duration
@@ -67,18 +66,18 @@ type Server struct {
connections map[net.Conn]struct{}
}
-// Run serves the http.Handler with graceful shutdown enabled.
+// Run serves the iris.Handler with graceful shutdown enabled.
//
// timeout is the duration to wait until killing active requests and stopping the server.
// If timeout is 0, the server never times out. It waits for all active requests to finish.
// we don't pass an iris.RequestHandler , because we need iris.station.server to be setted in order the station.Close() to work
-func Run(addr string, timeout time.Duration, n *iris.Iris) {
+func Run(addr string, timeout time.Duration, s *iris.Framework) {
srv := &Server{
Timeout: timeout,
- Logger: DefaultLogger(),
+ Logger: s.Logger,
+ station: s,
+ Server: s.NoListen(),
}
- srv.station = n
- srv.Server = srv.station.PreListen(config.Server{ListeningAddr: addr})
if err := srv.listenAndServe(); err != nil {
if opErr, ok := err.(*net.OpError); !ok || (ok && opErr.Op != "accept") {
@@ -92,13 +91,14 @@ func Run(addr string, timeout time.Duration, n *iris.Iris) {
//
// Unlike Run this version will not exit the program if an error is encountered but will
// return it instead.
-func RunWithErr(addr string, timeout time.Duration, n *iris.Iris) error {
+func RunWithErr(addr string, timeout time.Duration, s *iris.Framework) error {
srv := &Server{
Timeout: timeout,
- Logger: DefaultLogger(),
+ Logger: s.Logger,
+ station: s,
+ Server: s.NoListen(),
}
- srv.station = n
- srv.Server = srv.station.PreListen(config.Server{ListeningAddr: addr})
+
return srv.listenAndServe()
}
@@ -143,7 +143,7 @@ func (srv *Server) serve(listener net.Listener) error {
// Serve with graceful listener.
// Execution blocks here until listener.Close() is called, above.
- srv.station.PostListen()
+ srv.station.NoListen()
err := srv.Server.Serve(listener)
if err != nil {
// If the underlying listening is closed, Serve returns an error
@@ -218,7 +218,7 @@ func (srv *Server) manageConnections(add, remove chan net.Conn, shutdown chan ch
case <-kill:
for k := range srv.connections {
if err := k.Close(); err != nil {
- srv.log("[IRIS GRACEFUL ERROR] %s", err.Error())
+ srv.log("[IRIS GRACEFUL ERROR]" + err.Error())
}
}
return
@@ -252,7 +252,7 @@ func (srv *Server) handleInterrupt(interrupt chan os.Signal, quitting chan struc
close(quitting)
srv.Server.DisableKeepalive = true
if err := listener.Close(); err != nil {
- srv.log("[IRIS GRACEFUL ERROR] %s", err.Error())
+ srv.log("[IRIS GRACEFUL ERROR]" + err.Error())
}
if srv.ShutdownInitiated != nil {
diff --git a/handler.go b/handler.go
deleted file mode 100644
index 0ea29fe8..00000000
--- a/handler.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package iris
-
-import (
- "net/http"
-
- "github.com/valyala/fasthttp"
- "github.com/valyala/fasthttp/fasthttpadaptor"
-)
-
-type (
-
- // Handler the main Iris Handler interface.
- Handler interface {
- Serve(ctx *Context)
- }
-
- // HandlerFunc type is an adapter to allow the use of
- // ordinary functions as HTTP handlers. If f is a function
- // with the appropriate signature, HandlerFunc(f) is a
- // Handler that calls f.
- HandlerFunc func(*Context)
-
- // HandlerAPI allow the use of a custom struct as API handler(s) for a particular request path
- // It's just an interface {}, we keep it here to make things more readable.
- HandlerAPI interface {
- // we don't use context.IContext because of some methods as Get() is already inside the IContext interface and conficts with the Get()
- // we want to use for API.
- // a valid controller has this form:
- /*
- type index struct {
- *iris.Context
- }
-
- // OR
- type index struct {
- Context *iris.Context
- }
-
- func (i index) Get() {
- i.Write("Hello from /")
- }
-
- func (i index) GetBy(id string) {} // /:namedParameter
- //POST,PUT,DELETE...
-
- */
- }
-
- //IMiddlewareSupporter is an interface which all routers must implement
- IMiddlewareSupporter interface {
- Use(handlers ...Handler)
- UseFunc(handlersFn ...HandlerFunc)
- }
-
- // Middleware is just a slice of Handler []func(c *Context)
- Middleware []Handler
-)
-
-// Serve serves the handler, is like ServeHTTP for Iris
-func (h HandlerFunc) Serve(ctx *Context) {
- h(ctx)
-}
-
-// ToHandler converts an http.Handler or http.HandlerFunc to an iris.Handler
-func ToHandler(handler interface{}) Handler {
- //this is not the best way to do it, but I dont have any options right now.
- switch handler.(type) {
- case Handler:
- //it's already an iris handler
- return handler.(Handler)
- case http.Handler:
- //it's http.Handler
- h := fasthttpadaptor.NewFastHTTPHandlerFunc(handler.(http.Handler).ServeHTTP)
-
- return ToHandlerFastHTTP(h)
- case func(http.ResponseWriter, *http.Request):
- //it's http.HandlerFunc
- h := fasthttpadaptor.NewFastHTTPHandlerFunc(handler.(func(http.ResponseWriter, *http.Request)))
- return ToHandlerFastHTTP(h)
- default:
- panic(ErrHandler.Format(handler, handler))
- }
-}
-
-// ToHandlerFunc converts an http.Handler or http.HandlerFunc to an iris.HandlerFunc
-func ToHandlerFunc(handler interface{}) HandlerFunc {
- return ToHandler(handler).Serve
-}
-
-// ToHandlerFastHTTP converts an fasthttp.RequestHandler to an iris.Handler
-func ToHandlerFastHTTP(h fasthttp.RequestHandler) Handler {
- return HandlerFunc((func(ctx *Context) {
- h(ctx.RequestCtx)
- }))
-}
-
-// ConvertToHandlers accepts list of HandlerFunc and returns list of Handler
-// this can be renamed to convertToMiddleware also because it returns a list of []Handler which is what Middleware is
-func ConvertToHandlers(handlersFn []HandlerFunc) []Handler {
- hlen := len(handlersFn)
- mlist := make([]Handler, hlen)
- for i := 0; i < hlen; i++ {
- mlist[i] = Handler(handlersFn[i])
- }
- return mlist
-}
-
-// JoinMiddleware uses to create a copy of all middleware and return them in order to use inside the node
-func JoinMiddleware(middleware1 Middleware, middleware2 Middleware) Middleware {
- nowLen := len(middleware1)
- totalLen := nowLen + len(middleware2)
- // create a new slice of middleware in order to store all handlers, the already handlers(middleware) and the new
- newMiddleware := make(Middleware, totalLen)
- //copy the already middleware to the just created
- copy(newMiddleware, middleware1)
- //start from there we finish, and store the new middleware too
- copy(newMiddleware[nowLen:], middleware2)
- return newMiddleware
-}
diff --git a/http.go b/http.go
new file mode 100644
index 00000000..9ada71af
--- /dev/null
+++ b/http.go
@@ -0,0 +1,1303 @@
+package iris
+
+import (
+ "bytes"
+ "net"
+ "net/http"
+ "net/http/pprof"
+ "os"
+ "strings"
+ "sync"
+
+ "github.com/kataras/iris/config"
+ "github.com/kataras/iris/errors"
+ "github.com/kataras/iris/utils"
+ "github.com/valyala/fasthttp"
+ "github.com/valyala/fasthttp/fasthttpadaptor"
+)
+
+const (
+ // MethodGet "GET"
+ MethodGet = "GET"
+ // MethodPost "POST"
+ MethodPost = "POST"
+ // MethodPut "PUT"
+ MethodPut = "PUT"
+ // MethodDelete "DELETE"
+ MethodDelete = "DELETE"
+ // MethodConnect "CONNECT"
+ MethodConnect = "CONNECT"
+ // MethodHead "HEAD"
+ MethodHead = "HEAD"
+ // MethodPatch "PATCH"
+ MethodPatch = "PATCH"
+ // MethodOptions "OPTIONS"
+ MethodOptions = "OPTIONS"
+ // MethodTrace "TRACE"
+ MethodTrace = "TRACE"
+)
+
+var (
+ // AllMethods "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", "PATCH", "OPTIONS", "TRACE"
+ AllMethods = [...]string{MethodGet, MethodPost, MethodPut, MethodDelete, MethodConnect, MethodHead, MethodPatch, MethodOptions, MethodTrace}
+
+ /* methods as []byte, these are really used by iris */
+ // methodGetBytes "GET"
+ methodGetBytes = []byte(MethodGet)
+ // methodPostBytes "POST"
+ methodPostBytes = []byte(MethodPost)
+ // methodPutBytes "PUT"
+ methodPutBytes = []byte(MethodPut)
+ // methodDeleteBytes "DELETE"
+ methodDeleteBytes = []byte(MethodDelete)
+ // methodConnectBytes "CONNECT"
+ methodConnectBytes = []byte(MethodConnect)
+ // methodHeadBytes "HEAD"
+ methodHeadBytes = []byte(MethodHead)
+ // methodPatchBytes "PATCH"
+ methodPatchBytes = []byte(MethodPatch)
+ // methodOptionsBytes "OPTIONS"
+ methodOptionsBytes = []byte(MethodOptions)
+ // methodTraceBytes "TRACE"
+ methodTraceBytes = []byte(MethodTrace)
+ /* */
+
+)
+
+const (
+ // StatusContinue http status '100'
+ StatusContinue = 100
+ // StatusSwitchingProtocols http status '101'
+ StatusSwitchingProtocols = 101
+
+ // StatusOK http status '200'
+ StatusOK = 200
+ // StatusCreated http status '201'
+ StatusCreated = 201
+ // StatusAccepted http status '202'
+ StatusAccepted = 202
+ // StatusNonAuthoritativeInfo http status '203'
+ StatusNonAuthoritativeInfo = 203
+ // StatusNoContent http status '204'
+ StatusNoContent = 204
+ // StatusResetContent http status '205'
+ StatusResetContent = 205
+ // StatusPartialContent http status '206'
+ StatusPartialContent = 206
+
+ // StatusMultipleChoices http status '300'
+ StatusMultipleChoices = 300
+ // StatusMovedPermanently http status '301'
+ StatusMovedPermanently = 301
+ // StatusFound http status '302'
+ StatusFound = 302
+ // StatusSeeOther http status '303'
+ StatusSeeOther = 303
+ // StatusNotModified http status '304'
+ StatusNotModified = 304
+ // StatusUseProxy http status '305'
+ StatusUseProxy = 305
+ // StatusTemporaryRedirect http status '307'
+ StatusTemporaryRedirect = 307
+
+ // StatusBadRequest http status '400'
+ StatusBadRequest = 400
+ // StatusUnauthorized http status '401'
+ StatusUnauthorized = 401
+ // StatusPaymentRequired http status '402'
+ StatusPaymentRequired = 402
+ // StatusForbidden http status '403'
+ StatusForbidden = 403
+ // StatusNotFound http status '404'
+ StatusNotFound = 404
+ // StatusMethodNotAllowed http status '405'
+ StatusMethodNotAllowed = 405
+ // StatusNotAcceptable http status '406'
+ StatusNotAcceptable = 406
+ // StatusProxyAuthRequired http status '407'
+ StatusProxyAuthRequired = 407
+ // StatusRequestTimeout http status '408'
+ StatusRequestTimeout = 408
+ // StatusConflict http status '409'
+ StatusConflict = 409
+ // StatusGone http status '410'
+ StatusGone = 410
+ // StatusLengthRequired http status '411'
+ StatusLengthRequired = 411
+ // StatusPreconditionFailed http status '412'
+ StatusPreconditionFailed = 412
+ // StatusRequestEntityTooLarge http status '413'
+ StatusRequestEntityTooLarge = 413
+ // StatusRequestURITooLong http status '414'
+ StatusRequestURITooLong = 414
+ // StatusUnsupportedMediaType http status '415'
+ StatusUnsupportedMediaType = 415
+ // StatusRequestedRangeNotSatisfiable http status '416'
+ StatusRequestedRangeNotSatisfiable = 416
+ // StatusExpectationFailed http status '417'
+ StatusExpectationFailed = 417
+ // StatusTeapot http status '418'
+ StatusTeapot = 418
+ // StatusPreconditionRequired http status '428'
+ StatusPreconditionRequired = 428
+ // StatusTooManyRequests http status '429'
+ StatusTooManyRequests = 429
+ // StatusRequestHeaderFieldsTooLarge http status '431'
+ StatusRequestHeaderFieldsTooLarge = 431
+ // StatusUnavailableForLegalReasons http status '451'
+ StatusUnavailableForLegalReasons = 451
+
+ // StatusInternalServerError http status '500'
+ StatusInternalServerError = 500
+ // StatusNotImplemented http status '501'
+ StatusNotImplemented = 501
+ // StatusBadGateway http status '502'
+ StatusBadGateway = 502
+ // StatusServiceUnavailable http status '503'
+ StatusServiceUnavailable = 503
+ // StatusGatewayTimeout http status '504'
+ StatusGatewayTimeout = 504
+ // StatusHTTPVersionNotSupported http status '505'
+ StatusHTTPVersionNotSupported = 505
+ // StatusNetworkAuthenticationRequired http status '511'
+ StatusNetworkAuthenticationRequired = 511
+)
+
+var statusText = map[int]string{
+ StatusContinue: "Continue",
+ StatusSwitchingProtocols: "Switching Protocols",
+
+ StatusOK: "OK",
+ StatusCreated: "Created",
+ StatusAccepted: "Accepted",
+ StatusNonAuthoritativeInfo: "Non-Authoritative Information",
+ StatusNoContent: "No Content",
+ StatusResetContent: "Reset Content",
+ StatusPartialContent: "Partial Content",
+
+ StatusMultipleChoices: "Multiple Choices",
+ StatusMovedPermanently: "Moved Permanently",
+ StatusFound: "Found",
+ StatusSeeOther: "See Other",
+ StatusNotModified: "Not Modified",
+ StatusUseProxy: "Use Proxy",
+ StatusTemporaryRedirect: "Temporary Redirect",
+
+ StatusBadRequest: "Bad Request",
+ StatusUnauthorized: "Unauthorized",
+ StatusPaymentRequired: "Payment Required",
+ StatusForbidden: "Forbidden",
+ StatusNotFound: "Not Found",
+ StatusMethodNotAllowed: "Method Not Allowed",
+ StatusNotAcceptable: "Not Acceptable",
+ StatusProxyAuthRequired: "Proxy Authentication Required",
+ StatusRequestTimeout: "Request Timeout",
+ StatusConflict: "Conflict",
+ StatusGone: "Gone",
+ StatusLengthRequired: "Length Required",
+ StatusPreconditionFailed: "Precondition Failed",
+ StatusRequestEntityTooLarge: "Request Entity Too Large",
+ StatusRequestURITooLong: "Request URI Too Long",
+ StatusUnsupportedMediaType: "Unsupported Media Type",
+ StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
+ StatusExpectationFailed: "Expectation Failed",
+ StatusTeapot: "I'm a teapot",
+ StatusPreconditionRequired: "Precondition Required",
+ StatusTooManyRequests: "Too Many Requests",
+ StatusRequestHeaderFieldsTooLarge: "Request Header Fields Too Large",
+ StatusUnavailableForLegalReasons: "Unavailable For Legal Reasons",
+
+ StatusInternalServerError: "Internal Server Error",
+ StatusNotImplemented: "Not Implemented",
+ StatusBadGateway: "Bad Gateway",
+ StatusServiceUnavailable: "Service Unavailable",
+ StatusGatewayTimeout: "Gateway Timeout",
+ StatusHTTPVersionNotSupported: "HTTP Version Not Supported",
+ StatusNetworkAuthenticationRequired: "Network Authentication Required",
+}
+
+// StatusText returns a text for the HTTP status code. It returns the empty
+// string if the code is unknown.
+func StatusText(code int) string {
+ return statusText[code]
+}
+
+// Errors introduced by server.
+var (
+ errServerPortAlreadyUsed = errors.New("Server can't run, port is already used")
+ errServerAlreadyStarted = errors.New("Server is already started and listening")
+ errServerConfigMissing = errors.New("Empty Config for server")
+ errServerHandlerMissing = errors.New("Handler is missing from server, can't start without handler")
+ errServerIsClosed = errors.New("Can't close the server, propably is already closed or never started")
+ errServerRemoveUnix = errors.New("Unexpected error when trying to remove unix socket file. Addr: %s | Trace: %s")
+ errServerChmod = errors.New("Cannot chmod %#o for %q: %s")
+)
+
+// Server the http server
+type Server struct {
+ *fasthttp.Server
+ listener net.Listener
+ Config *config.Server
+ started bool
+ tls bool
+}
+
+// newServer returns a pointer to a Server object, and set it's options if any, nothing more
+func newServer(c *config.Server) *Server {
+ s := &Server{Server: &fasthttp.Server{Name: config.ServerName}, Config: c}
+ return s
+}
+
+// SetHandler sets the handler in order to listen on client requests
+func (s *Server) SetHandler(mux *serveMux) {
+ if s.Server != nil {
+ s.Server.Handler = mux.ServeRequest()
+ }
+}
+
+// IsListening returns true if server is listening/started, otherwise false
+func (s *Server) IsListening() bool {
+ return s.started && s.listener != nil && s.listener.Addr().String() != ""
+}
+
+// IsSecure returns true if server uses TLS, otherwise false
+func (s *Server) IsSecure() bool {
+ return s.tls
+}
+
+// Listener returns the net.Listener which this server (is) listening to
+func (s *Server) Listener() net.Listener {
+ return s.listener
+}
+
+// Host returns the Listener().Addr().String(), if server is not listening it returns the config.ListeningAddr
+func (s *Server) Host() (host string) {
+ if s.IsListening() {
+ return s.Listener().Addr().String()
+ }
+ return s.Config.ListeningAddr
+
+}
+
+// VirtualHost returns the s.Config.ListeningAddr
+//
+func (s *Server) VirtualHost() (host string) {
+ return s.Config.ListeningAddr
+}
+
+// Hostname returns the hostname part only, if host == localhost:8080 it will return the localhost
+// if server is not listening it returns the config.ListeningAddr's hostname part
+func (s *Server) Hostname() (hostname string) {
+ if s.IsListening() {
+ fullhost := s.Listener().Addr().String()
+ hostname = fullhost[0:strings.IndexByte(fullhost, ':')] // no the port
+ } else {
+ hostname = s.VirtualHostname()
+ }
+ return
+}
+
+// VirtualHostname returns the hostname that user registers, host path maybe differs from the real which is HostString, which taken from a net.listener
+func (s *Server) VirtualHostname() (hostname string) {
+ hostname = s.Config.ListeningAddr
+ if idx := strings.IndexByte(hostname, ':'); idx > 1 { // at least after second char
+ hostname = hostname[0:idx]
+ }
+ return
+}
+
+func (s *Server) listen() error {
+ if s.started {
+ return errServerAlreadyStarted.Return()
+ }
+
+ listener, err := net.Listen("tcp4", s.Config.ListeningAddr)
+
+ if err != nil {
+ return err
+ }
+
+ go s.serve(listener) // we don't catch underline errors, we catched all already
+ return nil
+
+}
+
+func (s *Server) listenUNIX() error {
+
+ mode := s.Config.Mode
+ addr := s.Config.ListeningAddr
+
+ if errOs := os.Remove(addr); errOs != nil && !os.IsNotExist(errOs) {
+ return errServerRemoveUnix.Format(s.Config.ListeningAddr, errOs.Error())
+ }
+
+ listener, err := net.Listen("unix", addr)
+
+ if err != nil {
+ return errServerPortAlreadyUsed.Return()
+ }
+
+ if err = os.Chmod(addr, mode); err != nil {
+ return errServerChmod.Format(mode, addr, err.Error())
+ }
+
+ go s.serve(listener) // we don't catch underline errors, we catched all already
+ return nil
+
+}
+
+//Serve just serves a listener, it is a blocking action, plugin.PostListen is not fired here.
+func (s *Server) serve(l net.Listener) error {
+ s.listener = l
+ s.started = true
+
+ if s.Config.CertFile != "" && s.Config.KeyFile != "" {
+ s.tls = true
+ return s.Server.ServeTLS(s.listener, s.Config.CertFile, s.Config.KeyFile)
+ }
+ s.tls = false
+ return s.Server.Serve(s.listener)
+}
+
+// open opens/starts/runs/listens (to) the server, listen tls if Cert && Key is registed, listenUNIX if Mode is registed, otherwise listen
+func (s *Server) open() error {
+ if s.started {
+ return errServerAlreadyStarted.Return()
+ }
+
+ if s.Config.ListeningAddr == "" {
+ return errServerConfigMissing.Return()
+ }
+
+ if s.Handler == nil {
+ return errServerHandlerMissing.Return()
+ }
+
+ // check the addr if :8080 do it 0.0.0.0:8080 ,we need the hostname for many cases
+ a := s.Config.ListeningAddr
+ //check if contains hostname, we need the full host, :8080 should be : 127.0.0.1:8080
+ if portIdx := strings.IndexByte(a, ':'); portIdx == 0 {
+ // then the : is the first letter, so we dont have setted a hostname, lets set it
+ s.Config.ListeningAddr = "127.0.0.1" + a
+ }
+
+ if s.Config.Mode > 0 {
+ return s.listenUNIX()
+ }
+ return s.listen()
+}
+
+// close closes the server
+func (s *Server) close() error {
+ if !s.started {
+ return errServerIsClosed.Return()
+ }
+
+ if s.listener != nil {
+ s.started = false
+ return s.listener.Close()
+ }
+ return nil
+}
+
+// errHandler returns na error with message: 'Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)
+// It seems to be a +type Points to: +pointer.'
+var errHandler = errors.New("Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)\n It seems to be a %T Points to: %v.")
+
+type (
+ // Handler the main Iris Handler interface.
+ Handler interface {
+ Serve(ctx *Context)
+ }
+
+ // HandlerFunc type is an adapter to allow the use of
+ // ordinary functions as HTTP handlers. If f is a function
+ // with the appropriate signature, HandlerFunc(f) is a
+ // Handler that calls f.
+ HandlerFunc func(*Context)
+ // Middleware is just a slice of Handler []func(c *Context)
+ Middleware []Handler
+
+ // HandlerAPI empty interface used for .API
+ HandlerAPI interface{}
+)
+
+// Serve implements the Handler, is like ServeHTTP but for Iris
+func (h HandlerFunc) Serve(ctx *Context) {
+ h(ctx)
+}
+
+// ToHandler converts an httapi.Handler or http.HandlerFunc to an iris.Handler
+func ToHandler(handler interface{}) Handler {
+ //this is not the best way to do it, but I dont have any options right now.
+ switch handler.(type) {
+ case Handler:
+ //it's already an iris handler
+ return handler.(Handler)
+ case http.Handler:
+ //it's http.Handler
+ h := fasthttpadaptor.NewFastHTTPHandlerFunc(handler.(http.Handler).ServeHTTP)
+
+ return ToHandlerFastHTTP(h)
+ case func(http.ResponseWriter, *http.Request):
+ //it's http.HandlerFunc
+ h := fasthttpadaptor.NewFastHTTPHandlerFunc(handler.(func(http.ResponseWriter, *http.Request)))
+ return ToHandlerFastHTTP(h)
+ default:
+ panic(errHandler.Format(handler, handler))
+ }
+}
+
+// ToHandlerFunc converts an http.Handler or http.HandlerFunc to an iris.HandlerFunc
+func ToHandlerFunc(handler interface{}) HandlerFunc {
+ return ToHandler(handler).Serve
+}
+
+// ToHandlerFastHTTP converts an fasthttp.RequestHandler to an iris.Handler
+func ToHandlerFastHTTP(h fasthttp.RequestHandler) Handler {
+ return HandlerFunc((func(ctx *Context) {
+ h(ctx.RequestCtx)
+ }))
+}
+
+// convertToHandlers just make []HandlerFunc to []Handler, although HandlerFunc and Handler are the same
+// we need this on some cases we explicit want a interface Handler, it is useless for users.
+func convertToHandlers(handlersFn []HandlerFunc) []Handler {
+ hlen := len(handlersFn)
+ mlist := make([]Handler, hlen)
+ for i := 0; i < hlen; i++ {
+ mlist[i] = Handler(handlersFn[i])
+ }
+ return mlist
+}
+
+// joinMiddleware uses to create a copy of all middleware and return them in order to use inside the node
+func joinMiddleware(middleware1 Middleware, middleware2 Middleware) Middleware {
+ nowLen := len(middleware1)
+ totalLen := nowLen + len(middleware2)
+ // create a new slice of middleware in order to store all handlers, the already handlers(middleware) and the new
+ newMiddleware := make(Middleware, totalLen)
+ //copy the already middleware to the just created
+ copy(newMiddleware, middleware1)
+ //start from there we finish, and store the new middleware too
+ copy(newMiddleware[nowLen:], middleware2)
+ return newMiddleware
+}
+
+func profileMiddleware(debugPath string) Middleware {
+ htmlMiddleware := HandlerFunc(func(ctx *Context) {
+ ctx.SetContentType(contentHTML + "; charset=" + config.Charset)
+ ctx.Next()
+ })
+ indexHandler := ToHandlerFunc(pprof.Index)
+ cmdlineHandler := ToHandlerFunc(pprof.Cmdline)
+ profileHandler := ToHandlerFunc(pprof.Profile)
+ symbolHandler := ToHandlerFunc(pprof.Symbol)
+ goroutineHandler := ToHandlerFunc(pprof.Handler("goroutine"))
+ heapHandler := ToHandlerFunc(pprof.Handler("heap"))
+ threadcreateHandler := ToHandlerFunc(pprof.Handler("threadcreate"))
+ debugBlockHandler := ToHandlerFunc(pprof.Handler("block"))
+
+ return Middleware{htmlMiddleware, HandlerFunc(func(ctx *Context) {
+ action := ctx.Param("action")
+ if len(action) > 1 {
+ if strings.Contains(action, "cmdline") {
+ cmdlineHandler.Serve((ctx))
+ } else if strings.Contains(action, "profile") {
+ profileHandler.Serve(ctx)
+ } else if strings.Contains(action, "symbol") {
+ symbolHandler.Serve(ctx)
+ } else if strings.Contains(action, "goroutine") {
+ goroutineHandler.Serve(ctx)
+ } else if strings.Contains(action, "heap") {
+ heapHandler.Serve(ctx)
+ } else if strings.Contains(action, "threadcreate") {
+ threadcreateHandler.Serve(ctx)
+ } else if strings.Contains(action, "debug/block") {
+ debugBlockHandler.Serve(ctx)
+ }
+ } else {
+ indexHandler.Serve(ctx)
+ }
+ })}
+}
+
+const (
+ // parameterStartByte is very used on the node, it's just contains the byte for the ':' rune/char
+ parameterStartByte = byte(':')
+ // slashByte is just a byte of '/' rune/char
+ slashByte = byte('/')
+ // slash is just a string of "/"
+ slash = "/"
+ // matchEverythingByte is just a byte of '*" rune/char
+ matchEverythingByte = byte('*')
+
+ isStatic entryCase = iota
+ isRoot
+ hasParams
+ matchEverything
+)
+
+type (
+ // PathParameter is a struct which contains Key and Value, used for named path parameters
+ PathParameter struct {
+ Key string
+ Value string
+ }
+
+ // PathParameters type for a slice of PathParameter
+ // Tt's a slice of PathParameter type, because it's faster than map
+ PathParameters []PathParameter
+
+ // entryCase is the type which the type of muxEntryusing in order to determinate what type (parameterized, anything, static...) is the perticular node
+ entryCase uint8
+
+ // muxEntry is the node of a tree of the routes,
+ // in order to learn how this is working, google 'trie' or watch this lecture: https://www.youtube.com/watch?v=uhAUk63tLRM
+ // this method is used by the BSD's kernel also
+ muxEntry struct {
+ part string
+ entryCase entryCase
+ hasWildNode bool
+ tokens string
+ nodes []*muxEntry
+ middleware Middleware
+ precedence uint64
+ paramsLen uint8
+ }
+)
+
+// Get returns a value from a key inside this Parameters
+// If no parameter with this key given then it returns an empty string
+func (params PathParameters) Get(key string) string {
+ for _, p := range params {
+ if p.Key == key {
+ return p.Value
+ }
+ }
+ return ""
+}
+
+// String returns a string implementation of all parameters that this PathParameters object keeps
+// hasthe form of key1=value1,key2=value2...
+func (params PathParameters) String() string {
+ var buff bytes.Buffer
+ for i := range params {
+ buff.WriteString(params[i].Key)
+ buff.WriteString("=")
+ buff.WriteString(params[i].Value)
+ if i < len(params)-1 {
+ buff.WriteString(",")
+ }
+
+ }
+ return buff.String()
+}
+
+// ParseParams receives a string and returns PathParameters (slice of PathParameter)
+// received string must have this form: key1=value1,key2=value2...
+func ParseParams(str string) PathParameters {
+ _paramsstr := strings.Split(str, ",")
+ if len(_paramsstr) == 0 {
+ return nil
+ }
+
+ params := make(PathParameters, 0) // PathParameters{}
+
+ // for i := 0; i < len(_paramsstr); i++ {
+ for i := range _paramsstr {
+ idxOfEq := strings.IndexRune(_paramsstr[i], '=')
+ if idxOfEq == -1 {
+ //error
+ return nil
+ }
+
+ key := _paramsstr[i][:idxOfEq]
+ val := _paramsstr[i][idxOfEq+1:]
+ params = append(params, PathParameter{key, val})
+ }
+ return params
+}
+
+// getParamsLen returns the parameters length from a given path
+func getParamsLen(path string) uint8 {
+ var n uint
+ for i := 0; i < len(path); i++ {
+ if path[i] != ':' && path[i] != '*' { // ParameterStartByte & MatchEverythingByte
+ continue
+ }
+ n++
+ }
+ if n >= 255 {
+ return 255
+ }
+ return uint8(n)
+}
+
+// add adds a muxEntry to the existing muxEntry or to the tree if no muxEntry has the prefix of
+func (e *muxEntry) add(path string, middleware Middleware) {
+ fullPath := path
+ e.precedence++
+ numParams := getParamsLen(path)
+
+ if len(e.part) > 0 || len(e.nodes) > 0 {
+ loop:
+ for {
+ if numParams > e.paramsLen {
+ e.paramsLen = numParams
+ }
+
+ i := 0
+ max := utils.FindLower(len(path), len(e.part))
+ for i < max && path[i] == e.part[i] {
+ i++
+ }
+
+ if i < len(e.part) {
+ node := muxEntry{
+ part: e.part[i:],
+ hasWildNode: e.hasWildNode,
+ tokens: e.tokens,
+ nodes: e.nodes,
+ middleware: e.middleware,
+ precedence: e.precedence - 1,
+ }
+
+ for i := range node.nodes {
+ if node.nodes[i].paramsLen > node.paramsLen {
+ node.paramsLen = node.nodes[i].paramsLen
+ }
+ }
+
+ e.nodes = []*muxEntry{&node}
+ e.tokens = string([]byte{e.part[i]})
+ e.part = path[:i]
+ e.middleware = nil
+ e.hasWildNode = false
+ }
+
+ if i < len(path) {
+ path = path[i:]
+
+ if e.hasWildNode {
+ e = e.nodes[0]
+ e.precedence++
+
+ if numParams > e.paramsLen {
+ e.paramsLen = numParams
+ }
+ numParams--
+
+ if len(path) >= len(e.part) && e.part == path[:len(e.part)] {
+
+ if len(e.part) >= len(path) || path[len(e.part)] == '/' {
+ continue loop
+ }
+ }
+
+ return
+ }
+
+ c := path[0]
+
+ if e.entryCase == hasParams && c == '/' && len(e.nodes) == 1 {
+ e = e.nodes[0]
+ e.precedence++
+ continue loop
+ }
+ //we need the i here to be re-setting, so use the same i variable as we declare it on line 176
+ for i := range e.tokens {
+ if c == e.tokens[i] {
+ i = e.precedenceTo(i)
+ e = e.nodes[i]
+ continue loop
+ }
+ }
+
+ if c != parameterStartByte && c != matchEverythingByte {
+
+ e.tokens += string([]byte{c})
+ node := &muxEntry{
+ paramsLen: numParams,
+ }
+ e.nodes = append(e.nodes, node)
+ e.precedenceTo(len(e.tokens) - 1)
+ e = node
+ }
+ e.addNode(numParams, path, fullPath, middleware)
+ return
+
+ } else if i == len(path) {
+ if e.middleware != nil {
+ return
+ }
+ e.middleware = middleware
+ }
+ return
+ }
+ } else {
+ e.addNode(numParams, path, fullPath, middleware)
+ e.entryCase = isRoot
+ }
+}
+
+// addNode adds a muxEntry as children to other muxEntry
+func (e *muxEntry) addNode(numParams uint8, path string, fullPath string, middleware Middleware) {
+ var offset int
+
+ for i, max := 0, len(path); numParams > 0; i++ {
+ c := path[i]
+ if c != parameterStartByte && c != matchEverythingByte {
+ continue
+ }
+
+ end := i + 1
+ for end < max && path[end] != '/' {
+ switch path[end] {
+ case parameterStartByte, matchEverythingByte:
+
+ default:
+ end++
+ }
+ }
+
+ if len(e.nodes) > 0 {
+ return
+ }
+
+ if end-i < 2 {
+ return
+ }
+
+ if c == parameterStartByte {
+
+ if i > 0 {
+ e.part = path[offset:i]
+ offset = i
+ }
+
+ child := &muxEntry{
+ entryCase: hasParams,
+ paramsLen: numParams,
+ }
+ e.nodes = []*muxEntry{child}
+ e.hasWildNode = true
+ e = child
+ e.precedence++
+ numParams--
+
+ if end < max {
+ e.part = path[offset:end]
+ offset = end
+
+ child := &muxEntry{
+ paramsLen: numParams,
+ precedence: 1,
+ }
+ e.nodes = []*muxEntry{child}
+ e = child
+ }
+
+ } else {
+ if end != max || numParams > 1 {
+ return
+ }
+
+ if len(e.part) > 0 && e.part[len(e.part)-1] == '/' {
+ return
+ }
+
+ i--
+ if path[i] != '/' {
+ return
+ }
+
+ e.part = path[offset:i]
+
+ child := &muxEntry{
+ hasWildNode: true,
+ entryCase: matchEverything,
+ paramsLen: 1,
+ }
+ e.nodes = []*muxEntry{child}
+ e.tokens = string(path[i])
+ e = child
+ e.precedence++
+
+ child = &muxEntry{
+ part: path[i:],
+ entryCase: matchEverything,
+ paramsLen: 1,
+ middleware: middleware,
+ precedence: 1,
+ }
+ e.nodes = []*muxEntry{child}
+
+ return
+ }
+ }
+
+ e.part = path[offset:]
+ e.middleware = middleware
+}
+
+// get is used by the Router, it finds and returns the correct muxEntry for a path
+func (e *muxEntry) get(path string, _params PathParameters) (middleware Middleware, params PathParameters, mustRedirect bool) {
+ params = _params
+loop:
+ for {
+ if len(path) > len(e.part) {
+ if path[:len(e.part)] == e.part {
+ path = path[len(e.part):]
+
+ if !e.hasWildNode {
+ c := path[0]
+ for i := range e.tokens {
+ if c == e.tokens[i] {
+ e = e.nodes[i]
+ continue loop
+ }
+ }
+
+ mustRedirect = (path == slash && e.middleware != nil)
+ return
+ }
+
+ e = e.nodes[0]
+ switch e.entryCase {
+ case hasParams:
+
+ end := 0
+ for end < len(path) && path[end] != '/' {
+ end++
+ }
+
+ if cap(params) < int(e.paramsLen) {
+ params = make(PathParameters, 0, e.paramsLen)
+ }
+ i := len(params)
+ params = params[:i+1]
+ params[i].Key = e.part[1:]
+ params[i].Value = path[:end]
+
+ if end < len(path) {
+ if len(e.nodes) > 0 {
+ path = path[end:]
+ e = e.nodes[0]
+ continue loop
+ }
+
+ mustRedirect = (len(path) == end+1)
+ return
+ }
+
+ if middleware = e.middleware; middleware != nil {
+ return
+ } else if len(e.nodes) == 1 {
+ e = e.nodes[0]
+ mustRedirect = (e.part == slash && e.middleware != nil)
+ }
+
+ return
+
+ case matchEverything:
+ if cap(params) < int(e.paramsLen) {
+ params = make(PathParameters, 0, e.paramsLen)
+ }
+ i := len(params)
+ params = params[:i+1]
+ params[i].Key = e.part[2:]
+ params[i].Value = path
+
+ middleware = e.middleware
+ return
+
+ default:
+ return
+ }
+ }
+ } else if path == e.part {
+ if middleware = e.middleware; middleware != nil {
+ return
+ }
+
+ if path == slash && e.hasWildNode && e.entryCase != isRoot {
+ mustRedirect = true
+ return
+ }
+
+ for i := range e.tokens {
+ if e.tokens[i] == '/' {
+ e = e.nodes[i]
+ mustRedirect = (len(e.part) == 1 && e.middleware != nil) ||
+ (e.entryCase == matchEverything && e.nodes[0].middleware != nil)
+ return
+ }
+ }
+
+ return
+ }
+
+ mustRedirect = (path == slash) ||
+ (len(e.part) == len(path)+1 && e.part[len(path)] == '/' &&
+ path == e.part[:len(e.part)-1] && e.middleware != nil)
+ return
+ }
+}
+
+// precedenceTo just adds the priority of this muxEntry by an index
+func (e *muxEntry) precedenceTo(index int) int {
+ e.nodes[index].precedence++
+ _precedence := e.nodes[index].precedence
+
+ newindex := index
+ for newindex > 0 && e.nodes[newindex-1].precedence < _precedence {
+ tmpN := e.nodes[newindex-1]
+ e.nodes[newindex-1] = e.nodes[newindex]
+ e.nodes[newindex] = tmpN
+
+ newindex--
+ }
+
+ if newindex != index {
+ e.tokens = e.tokens[:newindex] +
+ e.tokens[index:index+1] +
+ e.tokens[newindex:index] + e.tokens[index+1:]
+ }
+
+ return newindex
+}
+
+type (
+ // Route contains some useful information about a route
+ Route interface {
+ // Name returns the name of the route
+ Name() string
+ // Subdomain returns the subdomain,if any
+ Subdomain() string
+ // Method returns the http method
+ Method() string
+ // Path returns the path
+ Path() string
+ // Middleware returns the slice of Handler([]Handler) registed to this route
+ Middleware() Middleware
+ }
+
+ route struct {
+ // if no name given then it's the subdomain+path
+ name string
+ subdomain string
+ method string
+ path string
+ middleware Middleware
+ formattedPath string
+ formattedParts int
+ }
+)
+
+var _ Route = &route{}
+
+func newRoute(method []byte, subdomain string, path string, middleware Middleware) *route {
+ r := &route{name: path + subdomain, method: string(method), subdomain: subdomain, path: path, middleware: middleware}
+ r.formatPath()
+ return r
+}
+
+func (r *route) formatPath() {
+ // we don't care about performance here.
+ n1Len := strings.Count(r.path, ":")
+ isMatchEverything := len(r.path) > 0 && r.path[len(r.path)-1] == matchEverythingByte
+ if n1Len == 0 && !isMatchEverything {
+ // its a static
+ return
+ }
+ if n1Len == 0 && isMatchEverything {
+ //if we have something like: /mypath/anything/* -> /mypatch/anything/%v
+ r.formattedPath = r.path[0:len(r.path)-2] + "%v"
+ r.formattedParts++
+ return
+ }
+
+ tempPath := r.path
+ splittedN1 := strings.Split(r.path, "/")
+
+ for _, v := range splittedN1 {
+ if len(v) > 0 {
+ if v[0] == ':' || v[0] == matchEverythingByte {
+ r.formattedParts++
+ tempPath = strings.Replace(tempPath, v, "%v", -1) // n1Len, but let it we don't care about performance here.
+ }
+ }
+
+ }
+ r.formattedPath = tempPath
+}
+
+func (r *route) setName(newName string) {
+ r.name = newName
+}
+
+func (r route) Name() string {
+ return r.name
+}
+
+func (r route) Subdomain() string {
+ return r.subdomain
+}
+
+func (r route) Method() string {
+ return r.method
+}
+
+func (r route) Path() string {
+ return r.path
+}
+
+func (r route) Middleware() Middleware {
+ return r.middleware
+}
+
+const (
+ // subdomainIndicator where './' exists in a registed path then it contains subdomain
+ subdomainIndicator = "./"
+ // dynamicSubdomainIndicator where a registed path starts with '*.' then it contains a dynamic subdomain, if subdomain == "*." then its dynamic
+ dynamicSubdomainIndicator = "*."
+)
+
+type (
+ muxTree struct {
+ method []byte
+ // subdomain is empty for default-hostname routes,
+ // ex: mysubdomain.
+ subdomain string
+ entry *muxEntry
+ next *muxTree
+ }
+
+ serveMux struct {
+ cPool *sync.Pool
+ tree *muxTree
+ lookups []*route
+
+ api *muxAPI
+ errorHandlers map[int]Handler
+ // the main server host, ex: localhost, 127.0.0.1:8080, iris-go.com
+ host string
+ // if any of the trees contains not empty subdomain
+ hosts bool
+ // if false then searching by unescaped path
+ // defaults to true
+ escapePath bool
+ // if false then the /something it's not the same as /something/
+ // defaults to true
+ correctPath bool
+ mu sync.Mutex
+ }
+)
+
+func newServeMux(contextPool sync.Pool) *serveMux {
+ mux := &serveMux{
+ cPool: &contextPool,
+ lookups: make([]*route, 0),
+ errorHandlers: make(map[int]Handler, 0),
+ host: config.DefaultServerAddr,
+ escapePath: !config.DefaultDisablePathEscape,
+ correctPath: !config.DefaultDisablePathCorrection,
+ }
+
+ return mux
+}
+
+func (mux *serveMux) setHost(h string) {
+ mux.host = h
+}
+
+func (mux *serveMux) setEscapePath(b bool) {
+ mux.escapePath = b
+}
+
+func (mux *serveMux) setCorrectPath(b bool) {
+ mux.correctPath = b
+}
+
+// registerError registers a handler to a http status
+func (mux *serveMux) registerError(statusCode int, handler Handler) {
+ mux.mu.Lock()
+ func(statusCode int, handler Handler) {
+ mux.errorHandlers[statusCode] = HandlerFunc(func(ctx *Context) {
+ ctx.ResetBody()
+ ctx.SetStatusCode(statusCode)
+ handler.Serve(ctx)
+ })
+ }(statusCode, handler)
+ mux.mu.Unlock()
+}
+
+// fireError fires an error
+func (mux *serveMux) fireError(statusCode int, ctx *Context) {
+ mux.mu.Lock()
+ errHandler := mux.errorHandlers[statusCode]
+ if errHandler == nil {
+ errHandler = HandlerFunc(func(ctx *Context) {
+ ctx.ResetBody()
+ ctx.SetBodyString(statusText[statusCode])
+ ctx.SetStatusCode(statusCode)
+ })
+ mux.errorHandlers[statusCode] = errHandler
+ }
+ mux.mu.Unlock()
+
+ errHandler.Serve(ctx)
+}
+
+func (mux *serveMux) getTree(method []byte, subdomain string) (tree *muxTree) {
+ tree = mux.tree
+ for tree != nil {
+ if bytes.Equal(tree.method, method) && tree.subdomain == subdomain {
+ return
+ }
+ tree = tree.next
+ }
+ // tree is nil here, return that.
+ return
+}
+
+func (mux *serveMux) register(method []byte, subdomain string, path string, middleware Middleware) *route {
+ mux.mu.Lock()
+ defer mux.mu.Unlock()
+
+ if subdomain != "" {
+ mux.hosts = true
+ }
+ // add to the registry tree
+ tree := mux.getTree(method, subdomain)
+ if tree == nil {
+ //first time we register a route to this method with this domain
+ tree = &muxTree{method: method, subdomain: subdomain, entry: &muxEntry{}, next: nil}
+ if mux.tree == nil {
+ // it's the first entry
+ mux.tree = tree
+ } else {
+ // find the last tree and make the .next to the tree we created before
+ lastTree := mux.tree
+ for lastTree != nil {
+ if lastTree.next == nil {
+ lastTree.next = tree
+ break
+ }
+ lastTree = lastTree.next
+ }
+ }
+ }
+ // I decide that it's better to explicit give subdomain and a path to it than registedPath(mysubdomain./something) now its: subdomain: mysubdomain., path: /something
+ // we have different tree for each of subdomains, now you can use everyting you can use with the normal paths ( before you couldn't set /any/*path)
+ tree.entry.add(path, middleware)
+
+ // add to the lookups, it's just a collection of routes information
+ lookup := newRoute(method, subdomain, path, middleware)
+ mux.lookups = append(mux.lookups, lookup)
+
+ return lookup
+
+}
+
+func (mux *serveMux) lookup(routeName string) *route {
+ for i := range mux.lookups {
+ if r := mux.lookups[i]; r.name == routeName {
+ return r
+ }
+ }
+ return nil
+}
+
+func (mux *serveMux) ServeRequest() fasthttp.RequestHandler {
+ // optimize this once once, we could do that: context.RequestPath(mux.escapePath), but we lose some nanoseconds on if :)
+ getRequestPath := func(reqCtx *fasthttp.RequestCtx) string {
+ return utils.BytesToString(reqCtx.Path())
+ }
+ if !mux.escapePath {
+ getRequestPath = func(reqCtx *fasthttp.RequestCtx) string { return utils.BytesToString(reqCtx.RequestURI()) }
+ }
+
+ return func(reqCtx *fasthttp.RequestCtx) {
+ context := mux.cPool.Get().(*Context)
+ context.Reset(reqCtx)
+
+ routePath := getRequestPath(reqCtx)
+ tree := mux.tree
+ for tree != nil {
+ if !bytes.Equal(tree.method, reqCtx.Method()) {
+ // we break any CORS OPTIONS method
+ // but for performance reasons if user wants http method OPTIONS to be served
+ // then must register it with .Options(...)
+ tree = tree.next
+ continue
+ }
+ // we have at least one subdomain on the root
+ if mux.hosts && tree.subdomain != "" {
+ // context.VirtualHost() is a slow method because it makes string.Replaces but user can understand that if subdomain then server will have some nano/or/milleseconds performance cost
+ requestHost := context.VirtualHostname()
+ if requestHost != mux.host {
+ // we have a subdomain
+ if strings.Index(tree.subdomain, dynamicSubdomainIndicator) != -1 {
+ } else {
+ // mux.host = iris-go.com:8080, the subdomain for example is api.,
+ // so the host must be api.iris-go.com:8080
+ if tree.subdomain+mux.host != requestHost {
+ // go to the next tree, we have a subdomain but it is not the correct
+ tree = tree.next
+ continue
+ }
+
+ }
+ } else {
+ //("it's subdomain but the request is the same as the listening addr mux.host == requestHost =>" + mux.host + "=" + requestHost + " ____ and tree's subdomain was: " + tree.subdomain)
+ tree = tree.next
+ continue
+ }
+ }
+ middleware, params, mustRedirect := tree.entry.get(routePath, context.Params) // pass the parameters here for 0 allocation
+ if middleware != nil {
+ // ok we found the correct route, serve it and exit entirely from here
+ context.Params = params
+ context.middleware = middleware
+ //ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent)
+ context.Do()
+ mux.cPool.Put(context)
+ return
+ } else if mustRedirect && mux.correctPath && !bytes.Equal(reqCtx.Method(), methodConnectBytes) {
+
+ reqPath := routePath
+ pathLen := len(reqPath)
+
+ if pathLen > 1 {
+
+ if reqPath[pathLen-1] == '/' {
+ reqPath = reqPath[:pathLen-1] //remove the last /
+ } else {
+ //it has path prefix, it doesn't ends with / and it hasn't be found, then just add the slash
+ reqPath = reqPath + "/"
+ }
+
+ context.Request.URI().SetPath(reqPath)
+ urlToRedirect := utils.BytesToString(context.Request.RequestURI())
+
+ context.Redirect(urlToRedirect, StatusMovedPermanently) // StatusMovedPermanently
+ // RFC2616 recommends that a short note "SHOULD" be included in the
+ // response because older user agents may not understand 301/307.
+ // Shouldn't send the response for POST or HEAD; that leaves GET.
+ if bytes.Equal(tree.method, methodGetBytes) {
+ note := "Moved Permanently.\n"
+ context.Write(note)
+ }
+ mux.cPool.Put(context)
+ return
+ }
+ }
+ // not found
+ break
+ }
+ mux.fireError(StatusNotFound, context)
+ mux.cPool.Put(context)
+ }
+}
diff --git a/httperror.go b/httperror.go
deleted file mode 100644
index b704ff57..00000000
--- a/httperror.go
+++ /dev/null
@@ -1,228 +0,0 @@
-package iris
-
-//taken from net/http
-const (
- StatusContinue = 100
- StatusSwitchingProtocols = 101
-
- StatusOK = 200
- StatusCreated = 201
- StatusAccepted = 202
- StatusNonAuthoritativeInfo = 203
- StatusNoContent = 204
- StatusResetContent = 205
- StatusPartialContent = 206
-
- StatusMultipleChoices = 300
- StatusMovedPermanently = 301
- StatusFound = 302
- StatusSeeOther = 303
- StatusNotModified = 304
- StatusUseProxy = 305
- StatusTemporaryRedirect = 307
-
- StatusBadRequest = 400
- StatusUnauthorized = 401
- StatusPaymentRequired = 402
- StatusForbidden = 403
- StatusNotFound = 404
- StatusMethodNotAllowed = 405
- StatusNotAcceptable = 406
- StatusProxyAuthRequired = 407
- StatusRequestTimeout = 408
- StatusConflict = 409
- StatusGone = 410
- StatusLengthRequired = 411
- StatusPreconditionFailed = 412
- StatusRequestEntityTooLarge = 413
- StatusRequestURITooLong = 414
- StatusUnsupportedMediaType = 415
- StatusRequestedRangeNotSatisfiable = 416
- StatusExpectationFailed = 417
- StatusTeapot = 418
- StatusPreconditionRequired = 428
- StatusTooManyRequests = 429
- StatusRequestHeaderFieldsTooLarge = 431
- StatusUnavailableForLegalReasons = 451
-
- StatusInternalServerError = 500
- StatusNotImplemented = 501
- StatusBadGateway = 502
- StatusServiceUnavailable = 503
- StatusGatewayTimeout = 504
- StatusHTTPVersionNotSupported = 505
- StatusNetworkAuthenticationRequired = 511
-)
-
-var statusText = map[int]string{
- StatusContinue: "Continue",
- StatusSwitchingProtocols: "Switching Protocols",
-
- StatusOK: "OK",
- StatusCreated: "Created",
- StatusAccepted: "Accepted",
- StatusNonAuthoritativeInfo: "Non-Authoritative Information",
- StatusNoContent: "No Content",
- StatusResetContent: "Reset Content",
- StatusPartialContent: "Partial Content",
-
- StatusMultipleChoices: "Multiple Choices",
- StatusMovedPermanently: "Moved Permanently",
- StatusFound: "Found",
- StatusSeeOther: "See Other",
- StatusNotModified: "Not Modified",
- StatusUseProxy: "Use Proxy",
- StatusTemporaryRedirect: "Temporary Redirect",
-
- StatusBadRequest: "Bad Request",
- StatusUnauthorized: "Unauthorized",
- StatusPaymentRequired: "Payment Required",
- StatusForbidden: "Forbidden",
- StatusNotFound: "Not Found",
- StatusMethodNotAllowed: "Method Not Allowed",
- StatusNotAcceptable: "Not Acceptable",
- StatusProxyAuthRequired: "Proxy Authentication Required",
- StatusRequestTimeout: "Request Timeout",
- StatusConflict: "Conflict",
- StatusGone: "Gone",
- StatusLengthRequired: "Length Required",
- StatusPreconditionFailed: "Precondition Failed",
- StatusRequestEntityTooLarge: "Request Entity Too Large",
- StatusRequestURITooLong: "Request URI Too Long",
- StatusUnsupportedMediaType: "Unsupported Media Type",
- StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
- StatusExpectationFailed: "Expectation Failed",
- StatusTeapot: "I'm a teapot",
- StatusPreconditionRequired: "Precondition Required",
- StatusTooManyRequests: "Too Many Requests",
- StatusRequestHeaderFieldsTooLarge: "Request Header Fields Too Large",
- StatusUnavailableForLegalReasons: "Unavailable For Legal Reasons",
-
- StatusInternalServerError: "Internal Server Error",
- StatusNotImplemented: "Not Implemented",
- StatusBadGateway: "Bad Gateway",
- StatusServiceUnavailable: "Service Unavailable",
- StatusGatewayTimeout: "Gateway Timeout",
- StatusHTTPVersionNotSupported: "HTTP Version Not Supported",
- StatusNetworkAuthenticationRequired: "Network Authentication Required",
-}
-
-// StatusText returns a text for the HTTP status code. It returns the empty
-// string if the code is unknown.
-func StatusText(code int) string {
- return statusText[code]
-}
-
-//
-
-type (
-
- // HTTPErrorHandler is just an object which stores a http status code and a handler
- HTTPErrorHandler struct {
- code int
- handler HandlerFunc
- }
-
- // HTTPErrorContainer is the struct which contains the handlers which will execute if http error occurs
- // One struct per Server instance, the meaning of this is that the developer can change the default error message and replace them with his/her own completely custom handlers
- //
- // Example of usage:
- // iris.OnError(405, func (ctx *iris.Context){ c.SendStatus(405,"Method not allowed!!!")})
- // and inside the handler which you have access to the current Context:
- // ctx.EmitError(405)
- HTTPErrorContainer struct {
- // Errors contains all the httperrorhandlers
- Errors []*HTTPErrorHandler
- }
-)
-
-// HTTPErrorHandlerFunc creates a handler which is responsible to send a particular error to the client
-func HTTPErrorHandlerFunc(statusCode int, message string) HandlerFunc {
- return func(ctx *Context) {
- ctx.SetStatusCode(statusCode)
- ctx.SetBodyString(message)
- }
-}
-
-// GetCode returns the http status code value
-func (e *HTTPErrorHandler) GetCode() int {
- return e.code
-}
-
-// GetHandler returns the handler which is type of HandlerFunc
-func (e *HTTPErrorHandler) GetHandler() HandlerFunc {
- return e.handler
-}
-
-// SetHandler sets the handler (type of HandlerFunc) to this particular ErrorHandler
-func (e *HTTPErrorHandler) SetHandler(h HandlerFunc) {
- e.handler = h
-}
-
-// defaultHTTPErrors creates and returns an instance of HTTPErrorContainer with default handlers
-func defaultHTTPErrors() *HTTPErrorContainer {
- httperrors := new(HTTPErrorContainer)
- httperrors.Errors = make([]*HTTPErrorHandler, 0)
- httperrors.OnError(StatusNotFound, HTTPErrorHandlerFunc(StatusNotFound, statusText[StatusNotFound]))
- httperrors.OnError(StatusInternalServerError, HTTPErrorHandlerFunc(StatusInternalServerError, statusText[StatusInternalServerError]))
- return httperrors
-}
-
-// GetByCode returns the error handler by it's http status code
-func (he *HTTPErrorContainer) GetByCode(httpStatus int) *HTTPErrorHandler {
- if he != nil {
- for _, h := range he.Errors {
- if h.GetCode() == httpStatus {
- return h
- }
- }
- }
-
- return nil
-}
-
-// OnError Registers a handler for a specific http error status
-func (he *HTTPErrorContainer) OnError(httpStatus int, handler HandlerFunc) {
- if httpStatus == StatusOK {
- return
- }
-
- if errH := he.GetByCode(httpStatus); errH != nil {
-
- errH.SetHandler(handler)
- } else {
- he.Errors = append(he.Errors, &HTTPErrorHandler{code: httpStatus, handler: handler})
- }
-
-}
-
-///TODO: the errors must have .Next too, as middlewares inside the Context, if I let it as it is then we have problem
-// we cannot set a logger and a custom handler at one error because now the error handler takes only one handelrFunc and executes there from here...
-
-// EmitError executes the handler of the given error http status code
-func (he *HTTPErrorContainer) EmitError(errCode int, ctx *Context) {
- ctx.ResetBody()
- if errHandler := he.GetByCode(errCode); errHandler != nil {
- ctx.SetStatusCode(errCode) // for any case, user can change it after if want to
- errHandler.GetHandler().Serve(ctx)
- } else {
- //if no error is registed, then register it with the default http error text, and re-run the Emit
- he.OnError(errCode, func(c *Context) {
- c.SetStatusCode(errCode)
- c.SetBodyString(StatusText(errCode))
- })
- he.EmitError(errCode, ctx)
- }
-}
-
-// OnNotFound sets the handler for http status 404,
-// default is a response with text: 'Not Found' and status: 404
-func (he *HTTPErrorContainer) OnNotFound(handlerFunc HandlerFunc) {
- he.OnError(StatusNotFound, handlerFunc)
-}
-
-// OnPanic sets the handler for http status 500,
-// default is a response with text: The server encountered an unexpected condition which prevented it from fulfilling the request. and status: 500
-func (he *HTTPErrorContainer) OnPanic(handlerFunc HandlerFunc) {
- he.OnError(StatusInternalServerError, handlerFunc)
-}
diff --git a/initiatory.go b/initiatory.go
new file mode 100644
index 00000000..128d599d
--- /dev/null
+++ b/initiatory.go
@@ -0,0 +1,193 @@
+package iris
+
+import (
+ "fmt"
+ "os"
+ "sync"
+ "time"
+
+ "github.com/kataras/iris/config"
+ "github.com/kataras/iris/logger"
+ "github.com/kataras/iris/websocket"
+
+ "github.com/kataras/iris/mail"
+ "github.com/kataras/iris/render/rest"
+ "github.com/kataras/iris/render/template"
+ "github.com/kataras/iris/sessions"
+ ///NOTE: register the session providers, but the s.Config.Sessions.Provider will be used only, if this empty then sessions are disabled.
+ _ "github.com/kataras/iris/sessions/providers/memory"
+ _ "github.com/kataras/iris/sessions/providers/redis"
+)
+
+// Default entry, use it with iris.$anyPublicFunc
+var (
+ Default *Framework
+ Config *config.Iris
+ Logger *logger.Logger
+ Plugins PluginContainer
+ Websocket websocket.Server
+ HTTPServer *Server
+)
+
+func init() {
+ Default = New()
+ Config = Default.Config
+ Logger = Default.Logger
+ Plugins = Default.Plugins
+ Websocket = Default.Websocket
+ HTTPServer = Default.HTTPServer
+}
+
+const (
+ /* conversional */
+
+ // HTMLEngine conversion for config.HTMLEngine
+ HTMLEngine = config.HTMLEngine
+ // PongoEngine conversion for config.PongoEngine
+ PongoEngine = config.PongoEngine
+ // MarkdownEngine conversion for config.MarkdownEngine
+ MarkdownEngine = config.MarkdownEngine
+ // JadeEngine conversion for config.JadeEngine
+ JadeEngine = config.JadeEngine
+ // AmberEngine conversion for config.AmberEngine
+ AmberEngine = config.AmberEngine
+
+ // DefaultEngine conversion for config.DefaultEngine
+ DefaultEngine = config.DefaultEngine
+ // NoEngine conversion for config.NoEngine
+ NoEngine = config.NoEngine
+ // NoLayout to disable layout for a particular template file
+ // conversion for config.NoLayout
+ NoLayout = config.NoLayout
+
+ /* end conversional */
+)
+
+// Framework is our God |\| Google.Search('Greek mythology Iris')
+//
+// Implements the FrameworkAPI
+type Framework struct {
+ *muxAPI
+ rest *rest.Render
+ templates *template.Template
+ sessions *sessions.Manager
+ mailer mail.Service
+ // fields which are useful to the user/dev
+ HTTPServer *Server
+ Config *config.Iris
+ Logger *logger.Logger
+ Plugins PluginContainer
+ Websocket websocket.Server
+}
+
+// New creates and returns a new Iris station aka Framework.
+//
+// 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)
+
+ // 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}
+ {
+ ///NOTE: set all with s.Config pointer
+ // set the Logger
+ s.Logger = logger.New(s.Config.Logger)
+ // set the plugin container
+ s.Plugins = &pluginContainer{logger: s.Logger}
+ // set the websocket server
+ s.Websocket = websocket.NewServer(s.Config.Websocket)
+ // set the servemux, which will provide us the public API also, with its context pool
+ mux := newServeMux(sync.Pool{New: func() interface{} { return &Context{framework: s} }})
+ // set the public router API (and party)
+ s.muxAPI = &muxAPI{mux: mux, relativePath: "/"}
+ // set the server
+ s.HTTPServer = newServer(&s.Config.Server)
+ }
+
+ return s
+}
+
+func (s *Framework) initialize() {
+ // set sessions
+ if s.Config.Sessions.Provider != "" {
+ s.sessions = sessions.New(s.Config.Sessions)
+ }
+
+ //set the rest
+ s.rest = rest.New(s.Config.Render.Rest)
+
+ //set mail and templates if not already setted
+ s.prepareMailer()
+ s.prepareTemplates()
+
+ // listen to websocket connections
+ websocket.RegisterServer(s, s.Websocket, s.Logger)
+
+ // prepare the mux
+ s.mux.setCorrectPath(!s.Config.DisablePathCorrection)
+ s.mux.setEscapePath(!s.Config.DisablePathEscape)
+ s.mux.setHost(s.HTTPServer.VirtualHostname())
+ // set the debug profiling handlers if ProfilePath is setted
+ if debugPath := s.Config.ProfilePath; debugPath != "" {
+ s.Handle(MethodGet, debugPath+"/*action", profileMiddleware(debugPath)...)
+ }
+
+ // prepare the server
+ s.HTTPServer.SetHandler(s.mux)
+
+ if s.Config.MaxRequestBodySize > 0 {
+ s.HTTPServer.MaxRequestBodySize = int(s.Config.MaxRequestBodySize)
+ }
+}
+
+// prepareMailer sets the mailer if not nil, we make this check because of .SendMail, which can be called before Listen
+func (s *Framework) prepareMailer() {
+ // prepare the mail service
+ if s.mailer == nil {
+ s.mailer = mail.New(s.Config.Mail)
+ }
+}
+
+// prepareTemplates sets the templates if not nil, we make this check because of .TemplateString, which can be called before Listen
+func (s *Framework) prepareTemplates() {
+ // prepare the templates
+ if s.templates == nil {
+ // These functions are directly contact with Iris' functionality.
+ funcs := map[string]interface{}{
+ "url": s.URL,
+ "urlpath": s.Path,
+ }
+
+ template.RegisterSharedFuncs(funcs)
+
+ s.templates = template.New(s.Config.Render.Template)
+ }
+}
+
+// openServer is internal method, open the server with specific options passed by the Listen and ListenTLS
+// it's a blocking func
+func (s *Framework) openServer() (err error) {
+ s.initialize()
+ s.Plugins.DoPreListen(s)
+ if err = s.HTTPServer.open(); err == nil {
+ // print the banner
+ if !s.Config.DisableBanner {
+ s.Logger.PrintBanner(banner,
+ fmt.Sprintf("%s: Running at %s\n", time.Now().Format(config.TimeFormat),
+ s.HTTPServer.Host()))
+ }
+ s.Plugins.DoPostListen(s)
+ ch := make(chan os.Signal)
+ <-ch
+ s.Close()
+ }
+ return
+}
+
+// closeServer is used to close the tcp listener from the server, returns an error
+func (s *Framework) closeServer() error {
+ s.Plugins.DoPreClose(s)
+ return s.HTTPServer.close()
+}
diff --git a/iris.go b/iris.go
index c43d52f2..51adc030 100644
--- a/iris.go
+++ b/iris.go
@@ -1,33 +1,75 @@
-// Package iris the fastest golang web framework, so far.
+//Package iris the fastest go web framework in (this) Earth.
+///NOTE: When you see 'framework' or 'station' we mean the Iris web framework's main implementation.
//
-// Note: When 'Station', we mean the Iris type.
+//
+// Basic usage
+// ----------------------------------------------------------------------
+//
+// package main
+//
+// import "github.com/kataras/iris"
+//
+// func main() {
+// iris.Get("/hi_json", func(c *iris.Context) {
+// c.JSON(200, iris.Map{
+// "Name": "Iris",
+// "Age": 2,
+// })
+// })
+// iris.Listen(":8080")
+// }
+//
+// ----------------------------------------------------------------------
+//
+// package main
+//
+// import "github.com/kataras/iris"
+//
+// func main() {
+// s1 := iris.New()
+// s1.Get("/hi_json", func(c *iris.Context) {
+// c.JSON(200, iris.Map{
+// "Name": "Iris",
+// "Age": 2,
+// })
+// })
+//
+// s2 := iris.New()
+// s2.Get("/hi_raw_html", func(c *iris.Context) {
+// c.HTML(iris.StatusOK, " Iris welcomes
you!
")
+// })
+//
+// go s1.Listen(":8080")
+// s2.Listen(":1993")
+// }
+//
+// -----------------------------DOCUMENTATION----------------------------
+// ----------------------------_______________---------------------------
+// For middleware, templates, sessions, websockets, mails, subdomains,
+// dynamic subdomains, routes, party of subdomains & routes and much more
+// visit https://www.gitbook.com/book/kataras/iris/details
package iris
import (
+ "fmt"
"os"
- "sync"
+ "path"
+ "reflect"
+ "strconv"
+ "strings"
"time"
- "github.com/kataras/iris/config"
- "github.com/kataras/iris/logger"
- "github.com/kataras/iris/mail"
- "github.com/kataras/iris/render/rest"
- "github.com/kataras/iris/render/template"
- "github.com/kataras/iris/server"
- "github.com/kataras/iris/sessions"
- // memory loads the memory session provider
- _ "github.com/kataras/iris/sessions/providers/memory"
- // _ redis loads the redis session provider
- "fmt"
+ "github.com/kataras/iris/errors"
- _ "github.com/kataras/iris/sessions/providers/redis"
- "github.com/kataras/iris/websocket"
- "github.com/klauspost/compress/gzip"
+ "github.com/kataras/iris/config"
+ "github.com/kataras/iris/context"
+ "github.com/kataras/iris/utils"
+ "github.com/valyala/fasthttp"
)
const (
// Version of the iris
- Version = "v3.0.0-beta.4"
+ Version = "3.0.0-rc.1"
banner = ` _____ _
|_ _| (_)
| | ____ _ ___
@@ -37,288 +79,1153 @@ const (
`
)
-/* for conversion */
-
-var (
- // HTMLEngine conversion for config.HTMLEngine
- HTMLEngine = config.HTMLEngine
- // PongoEngine conversion for config.PongoEngine
- PongoEngine = config.PongoEngine
- // MarkdownEngine conversion for config.MarkdownEngine
- MarkdownEngine = config.MarkdownEngine
- // JadeEngine conversion for config.JadeEngine
- JadeEngine = config.JadeEngine
- // AmberEngine conversion for config.AmberEngine
- AmberEngine = config.AmberEngine
-
- // DefaultEngine conversion for config.DefaultEngine
- DefaultEngine = config.DefaultEngine
- // NoEngine conversion for config.NoEngine
- NoEngine = config.NoEngine
- // NoLayout to disable layout for a particular template file
- // conversion for config.NoLayout
- NoLayout = config.NoLayout
-)
-
-/* */
-
type (
+ // FrameworkAPI contains the main Iris Public API
+ FrameworkAPI interface {
+ MuxAPI
+ Must(error)
+ ListenWithErr(string) error
+ Listen(string)
+ ListenTLSWithErr(string, string, string) error
+ ListenTLS(string, string, string)
+ ListenUNIXWithErr(string, os.FileMode) error
+ ListenUNIX(string, os.FileMode)
+ NoListen() *Server
+ Close()
+ OnError(int, HandlerFunc)
+ EmitError(int, *Context)
+ Lookup(string) Route
+ Lookups() []Route
+ Path(string, ...interface{}) string
+ URL(string, ...interface{}) string
+ SendMail(string, string, ...string) error
+ TemplateString(string, interface{}, ...string) string
+ }
- // Iris is the container of all, server, router, cache and the sync.Pool
- Iris struct {
- *router
- config *config.Iris
- server *server.Server
- plugins *PluginContainer
- rest *rest.Render
- templates *template.Template
- sessionManager *sessions.Manager
- websocketServer websocket.Server
- mailService mail.Service
- logger *logger.Logger
- gzipWriterPool sync.Pool // this pool is used everywhere needed in the iris for example inside party-> StaticSimple
+ // RouteNameFunc the func returns from the MuxAPi's methods, optionally sets the name of the Route (*route)
+ RouteNameFunc func(string)
+ // MuxAPI the visible api for the serveMux
+ MuxAPI interface {
+ Party(string, ...HandlerFunc) MuxAPI
+
+ Use(...Handler)
+ UseFunc(...HandlerFunc)
+ Handle(string, string, ...Handler) RouteNameFunc
+ HandleFunc(string, string, ...HandlerFunc) RouteNameFunc
+ // H_ is used to convert a context.IContext handler func to iris.HandlerFunc, is used only inside iris internal package to avoid import cycles
+ H_(string, string, func(context.IContext)) func(string)
+ API(string, HandlerAPI, ...HandlerFunc)
+
+ Get(string, ...HandlerFunc) RouteNameFunc
+ Post(string, ...HandlerFunc) RouteNameFunc
+ Put(string, ...HandlerFunc) RouteNameFunc
+ Delete(string, ...HandlerFunc) RouteNameFunc
+ Connect(string, ...HandlerFunc) RouteNameFunc
+ Head(string, ...HandlerFunc) RouteNameFunc
+ Options(string, ...HandlerFunc) RouteNameFunc
+ Patch(string, ...HandlerFunc) RouteNameFunc
+ Trace(string, ...HandlerFunc) RouteNameFunc
+ Any(string, ...HandlerFunc)
+
+ StaticHandler(string, int, bool, bool, []string) HandlerFunc
+ Static(string, string, int) RouteNameFunc
+ StaticFS(string, string, int) RouteNameFunc
+ StaticWeb(string, string, int) RouteNameFunc
+ StaticServe(string, ...string) RouteNameFunc
+ StaticContent(string, string, []byte) func(string)
+ Favicon(string, ...string) RouteNameFunc
}
)
-// New creates and returns a new iris station.
-//
-// Receives an optional config.Iris as parameter
-// If empty then config.Default() is used instead
-func New(cfg ...config.Iris) *Iris {
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// --------------------------------Framework implementation-----------------------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
- c := config.Default().Merge(cfg)
+var _ FrameworkAPI = &Framework{}
- // create the Iris
- s := &Iris{config: &c}
-
- // create & set the router
- s.router = newRouter(s)
-
- // set the Logger
- s.logger = logger.New(c.Logger)
-
- //set the plugin container
- s.plugins = &PluginContainer{logger: s.logger}
-
- // set the gzip writer pool
- s.gzipWriterPool = sync.Pool{New: func() interface{} { return &gzip.Writer{} }}
- return s
+// Must panics on error, it panics on registed iris' logger
+func Must(err error) {
+ Default.Must(err)
}
-// newContextPool returns a new context pool, internal method used in tree and router
-func (s *Iris) newContextPool() sync.Pool {
- return sync.Pool{New: func() interface{} {
- return &Context{station: s}
- }}
-}
-
-///TODO HERE MOVE THEM SOMEWHERE ELSE OR MAKE IT MORE BUETY->
-func (s *Iris) initTemplates() {
- if s.templates == nil { // because if .Templates() called before server's listen, s.templates != nil when PreListen
- // init the templates
- template.RegisterSharedFunc("url", func(routeName string, args ...interface{}) string {
- if url, err := s.UriOf(routeName, args...); err == nil {
- return url
- } else {
- return err.Error()
- }
- })
-
- template.RegisterSharedFunc("urlpath", func(routeName string, args ...interface{}) string {
- return s.RouteByName(routeName).ParsePath(args...)
- })
-
- s.templates = template.New(s.config.Render.Template)
-
+// Must panics on error, it panics on registed iris' logger
+func (s *Framework) Must(err error) {
+ if err != nil {
+ s.Logger.Panic(err.Error())
}
-
-}
-
-func (s *Iris) initWebsocketServer() {
- if s.websocketServer == nil {
- // enable websocket if config.Websocket.Endpoint != ""
- if s.config.Websocket.Endpoint != "" {
- s.websocketServer = websocket.New(s, s.config.Websocket)
- }
- }
-}
-
-func (s *Iris) initMailService() {
- if s.mailService == nil {
- // enable mail sender service if configs are valid
- if s.config.Mail.IsValid() {
- s.mailService = mail.New(s.config.Mail)
- }
- }
-}
-
-// PreListen call router's optimize, sets the server's handler and notice the plugins
-// capital because we need it sometimes, for example inside the graceful
-// receives the config.Server
-// returns the station's Server (*server.Server)
-// it's a non-blocking func
-func (s *Iris) PreListen(opt config.Server) *server.Server {
- // router preparation, runs only once even if called more than one time.
- if !s.router.optimized {
- s.router.optimize()
-
- s.server = server.New(opt)
- s.server.SetHandler(s.router.ServeRequest)
-
- if s.config.MaxRequestBodySize > 0 {
- s.server.MaxRequestBodySize = int(s.config.MaxRequestBodySize)
- }
- }
-
- if !s.config.DisableBanner {
- s.logger.PrintBanner(banner, fmt.Sprintf("%s: Running at %s\n", time.Now().Format(config.TimeFormat), s.server.Config.ListeningAddr))
- }
-
- s.plugins.DoPreListen(s)
-
- return s.server
-}
-
-// PostListen sets the rest render, template engine, sessions and notice the plugins
-// capital because we need it sometimes, for example inside the graceful
-// it's a non-blocking func
-func (s *Iris) PostListen() {
- //if not error opening the server, then:
-
- //set the rest (for Data, Text, JSON, JSONP, XML)
- s.rest = rest.New(s.config.Render.Rest)
- // set the templates
- s.initTemplates()
- // set the session manager if we have a provider
- if s.config.Sessions.Provider != "" {
- s.sessionManager = sessions.New(s.config.Sessions)
- }
-
- // set the websocket
- s.initWebsocketServer()
-
- s.plugins.DoPostListen(s)
-}
-
-// listen is internal method, open the server with specific options passed by the Listen and ListenTLS
-// it's a blocking func
-func (s *Iris) listen(opt config.Server) (err error) {
- s.PreListen(opt)
-
- if err = s.server.OpenServer(); err == nil {
- s.PostListen()
-
- ch := make(chan os.Signal)
- <-ch
- s.Close()
- }
- return
}
// ListenWithErr starts the standalone http server
// which listens to the addr parameter which as the form of
-// host:port or just port
+// host:port
//
// It returns an error you are responsible how to handle this
// if you need a func to panic on error use the Listen
// ex: log.Fatal(iris.ListenWithErr(":8080"))
-func (s *Iris) ListenWithErr(addr string) error {
- opt := config.Server{ListeningAddr: addr}
- return s.listen(opt)
+func ListenWithErr(addr string) error {
+ return Default.ListenWithErr(addr)
}
// Listen starts the standalone http server
// which listens to the addr parameter which as the form of
-// host:port or just port
+// host:port
//
// It panics on error if you need a func to return an error use the ListenWithErr
// ex: iris.Listen(":8080")
-func (s *Iris) Listen(addr string) {
- if err := s.ListenWithErr(addr); err != nil {
- panic(err)
- }
+func Listen(addr string) {
+ Default.Listen(addr)
+}
+
+// ListenWithErr starts the standalone http server
+// which listens to the addr parameter which as the form of
+// host:port
+//
+// It returns an error you are responsible how to handle this
+// if you need a func to panic on error use the Listen
+// ex: log.Fatal(iris.ListenWithErr(":8080"))
+func (s *Framework) ListenWithErr(addr string) error {
+ s.Config.Server.ListeningAddr = addr
+ return s.openServer()
+}
+
+// Listen starts the standalone http server
+// which listens to the addr parameter which as the form of
+// host:port
+//
+// It panics on error if you need a func to return an error use the ListenWithErr
+// ex: iris.Listen(":8080")
+func (s *Framework) Listen(addr string) {
+ s.Must(s.ListenWithErr(addr))
}
// ListenTLSWithErr Starts a https server with certificates,
// if you use this method the requests of the form of 'http://' will fail
// only https:// connections are allowed
// which listens to the addr parameter which as the form of
-// host:port or just port
+// host:port
//
// It returns an error you are responsible how to handle this
// if you need a func to panic on error use the ListenTLS
// ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key"))
-func (s *Iris) ListenTLSWithErr(addr string, certFile, keyFile string) error {
- opt := config.Server{ListeningAddr: addr, CertFile: certFile, KeyFile: keyFile}
- return s.listen(opt)
+func ListenTLSWithErr(addr string, certFile string, keyFile string) error {
+ return Default.ListenTLSWithErr(addr, certFile, keyFile)
}
// ListenTLS Starts a https server with certificates,
// if you use this method the requests of the form of 'http://' will fail
// only https:// connections are allowed
// which listens to the addr parameter which as the form of
-// host:port or just port
+// host:port
//
// It panics on error if you need a func to return an error use the ListenTLSWithErr
// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key")
-func (s *Iris) ListenTLS(addr string, certFile, keyFile string) {
- if err := s.ListenTLSWithErr(addr, certFile, keyFile); err != nil {
- panic(err)
- }
+func ListenTLS(addr string, certFile string, keyFile string) {
+ Default.ListenTLS(addr, certFile, keyFile)
}
-// CloseWithErr is used to close the tcp listener from the server, returns an error
-func (s *Iris) CloseWithErr() error {
- s.plugins.DoPreClose(s)
- return s.server.CloseServer()
+// ListenTLSWithErr Starts a https server with certificates,
+// if you use this method the requests of the form of 'http://' will fail
+// only https:// connections are allowed
+// which listens to the addr parameter which as the form of
+// host:port
+//
+// It returns an error you are responsible how to handle this
+// if you need a func to panic on error use the ListenTLS
+// ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key"))
+func (s *Framework) ListenTLSWithErr(addr string, certFile string, keyFile string) error {
+ s.Config.Server.ListeningAddr = addr
+ s.Config.Server.CertFile = certFile
+ s.Config.Server.KeyFile = keyFile
+ return s.openServer()
+}
+
+// ListenTLS Starts a https server with certificates,
+// if you use this method the requests of the form of 'http://' will fail
+// only https:// connections are allowed
+// which listens to the addr parameter which as the form of
+// host:port
+//
+// It panics on error if you need a func to return an error use the ListenTLSWithErr
+// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key")
+func (s *Framework) ListenTLS(addr string, certFile, keyFile string) {
+ s.Must(s.ListenTLSWithErr(addr, certFile, keyFile))
+}
+
+// ListenUNIXWithErr starts the process of listening to the new requests using a 'socket file', this works only on unix
+// returns an error if something bad happens when trying to listen to
+func ListenUNIXWithErr(addr string, mode os.FileMode) error {
+ return Default.ListenUNIXWithErr(addr, mode)
+}
+
+// ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix
+// panics on error
+func ListenUNIX(addr string, mode os.FileMode) {
+ Default.ListenUNIX(addr, mode)
+}
+
+// ListenUNIXWithErr starts the process of listening to the new requests using a 'socket file', this works only on unix
+// returns an error if something bad happens when trying to listen to
+func (s *Framework) ListenUNIXWithErr(addr string, mode os.FileMode) error {
+ s.Config.Server.ListeningAddr = addr
+ s.Config.Server.Mode = mode
+ return s.openServer()
+}
+
+// ListenUNIX starts the process of listening to the new requests using a 'socket file', this works only on unix
+// panics on error
+func (s *Framework) ListenUNIX(addr string, mode os.FileMode) {
+ s.Must(s.ListenUNIXWithErr(addr, mode))
+}
+
+// NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
+func NoListen() *Server {
+ return Default.NoListen()
+}
+
+// NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it
+func (s *Framework) NoListen() *Server {
+ s.initialize()
+ s.Plugins.DoPreListen(s)
+ s.Plugins.DoPostListen(s)
+ return s.HTTPServer
}
//Close terminates the server and panic if error occurs
-func (s *Iris) Close() {
- if err := s.CloseWithErr(); err != nil {
- panic(err)
+func Close() {
+ Default.Close()
+}
+
+//Close terminates the server and panic if error occurs
+func (s *Framework) Close() {
+ s.Must(s.closeServer())
+}
+
+// OnError registers a custom http error handler
+func OnError(statusCode int, handlerFn HandlerFunc) {
+ Default.OnError(statusCode, handlerFn)
+}
+
+// EmitError fires a custom http error handler to the client
+//
+// if no custom error defined with this statuscode, then iris creates one, and once at runtime
+func EmitError(statusCode int, ctx *Context) {
+ Default.EmitError(statusCode, ctx)
+}
+
+// OnError registers a custom http error handler
+func (s *Framework) OnError(statusCode int, handlerFn HandlerFunc) {
+ s.mux.registerError(statusCode, handlerFn)
+}
+
+// EmitError fires a custom http error handler to the client
+//
+// if no custom error defined with this statuscode, then iris creates one, and once at runtime
+func (s *Framework) EmitError(statusCode int, ctx *Context) {
+ s.mux.fireError(statusCode, ctx)
+}
+
+// Lookup returns a registed route by its name
+func Lookup(routeName string) Route {
+ return Default.Lookup(routeName)
+}
+
+// Lookups returns all registed routes
+func Lookups() []Route {
+ return Default.Lookups()
+}
+
+// Lookup returns a registed route by its name
+func (s *Framework) Lookup(routeName string) Route {
+ return s.mux.lookup(routeName)
+}
+
+// Lookups returns all registed routes
+func (s *Framework) Lookups() (routes []Route) {
+ // silly but...
+ for i := range s.mux.lookups {
+ routes = append(routes, s.mux.lookups[i])
+ }
+ return
+}
+
+// Path used to check arguments with the route's named parameters and return the correct url
+// if parse failed returns empty string
+func Path(routeName string, args ...interface{}) string {
+ return Default.Path(routeName, args...)
+}
+
+// Path used to check arguments with the route's named parameters and return the correct url
+// if parse failed returns empty string
+func (s *Framework) Path(routeName string, args ...interface{}) string {
+ r := s.mux.lookup(routeName)
+ if r == nil {
+ return ""
+ }
+
+ argsLen := len(args)
+
+ // we have named parameters but arguments not given
+ if argsLen == 0 && r.formattedParts > 0 {
+ return ""
+ } else if argsLen == 0 && r.formattedParts == 0 {
+ // it's static then just return the path
+ return r.path
+ }
+
+ // we have arguments but they are much more than the named parameters
+
+ // 1 check if we have /*, if yes then join all arguments to one as path and pass that as parameter
+ if argsLen > r.formattedParts {
+ if r.path[len(r.path)-1] == matchEverythingByte {
+ // we have to convert each argument to a string in this case
+
+ argsString := make([]string, argsLen, argsLen)
+
+ for i, v := range args {
+ if s, ok := v.(string); ok {
+ argsString[i] = s
+ } else if num, ok := v.(int); ok {
+ argsString[i] = strconv.Itoa(num)
+ } else if b, ok := v.(bool); ok {
+ argsString[i] = strconv.FormatBool(b)
+ } else if arr, ok := v.([]string); ok {
+ if len(arr) > 0 {
+ argsString[i] = arr[0]
+ argsString = append(argsString, arr[1:]...)
+ }
+ }
+ }
+
+ parameter := strings.Join(argsString, slash)
+ result := fmt.Sprintf(r.formattedPath, parameter)
+ return result
+ }
+ // 2 if !1 return false
+ return ""
+ }
+
+ arguments := args[0:]
+
+ // check for arrays
+ for i, v := range arguments {
+ if arr, ok := v.([]string); ok {
+ if len(arr) > 0 {
+ interfaceArr := make([]interface{}, len(arr))
+ for j, sv := range arr {
+ interfaceArr[j] = sv
+ }
+ arguments[i] = interfaceArr[0]
+ arguments = append(arguments, interfaceArr[1:]...)
+ }
+
+ }
+ }
+
+ return fmt.Sprintf(r.formattedPath, arguments...)
+}
+
+// URL returns the subdomain+ host + Path(...optional named parameters if route is dynamic)
+// returns an empty string if parse is failed
+func URL(routeName string, args ...interface{}) (url string) {
+ return Default.URL(routeName, args...)
+}
+
+// URL returns the subdomain+ host + Path(...optional named parameters if route is dynamic)
+// returns an empty string if parse is failed
+func (s *Framework) URL(routeName string, args ...interface{}) (url string) {
+ r := s.mux.lookup(routeName)
+ if r == nil {
+ return
+ }
+
+ scheme := "http://"
+ if s.HTTPServer.IsSecure() {
+ scheme = "https://"
+ }
+
+ host := s.HTTPServer.VirtualHost()
+ arguments := args[0:]
+
+ // join arrays as arguments
+ for i, v := range arguments {
+ if arr, ok := v.([]string); ok {
+ if len(arr) > 0 {
+ interfaceArr := make([]interface{}, len(arr))
+ for j, sv := range arr {
+ interfaceArr[j] = sv
+ }
+ arguments[i] = interfaceArr[0]
+ arguments = append(arguments, interfaceArr[1:]...)
+ }
+
+ }
+ }
+
+ // if it's dynamic subdomain then the first argument is the subdomain part
+ if r.subdomain == dynamicSubdomainIndicator {
+ if len(arguments) == 0 { // it's a wildcard subdomain but not arguments
+ return
+ }
+
+ if subdomain, ok := arguments[0].(string); ok {
+ host = subdomain + "." + host
+ } else {
+ // it is not array because we join them before. if not pass a string then this is not a subdomain part, return empty uri
+ return
+ }
+
+ arguments = arguments[1:]
+ }
+
+ if parsedPath := Path(routeName, arguments...); parsedPath != "" {
+ url = scheme + host + parsedPath
+ }
+
+ return
+}
+
+// SendMail sends a mail to recipients
+// the body can be html also
+func SendMail(subject string, body string, to ...string) error {
+ return Default.SendMail(subject, body, to...)
+}
+
+// SendMail sends a mail to recipients
+// the body can be html also
+func (s *Framework) SendMail(subject string, body string, to ...string) error {
+ s.prepareMailer()
+ return s.mailer.Send(subject, body, to...)
+}
+
+// TemplateString executes a template and returns its result as string, useful when you want it for sending rich e-mails
+// returns empty string on error
+func TemplateString(templateFile string, pageContext interface{}, layout ...string) string {
+ return Default.TemplateString(templateFile, pageContext, layout...)
+}
+
+// TemplateString executes a template and returns its result as string, useful when you want it for sending rich e-mails
+// returns empty string on error
+func (s *Framework) TemplateString(templateFile string, pageContext interface{}, layout ...string) string {
+ s.prepareTemplates()
+ res, err := s.templates.RenderString(templateFile, pageContext, layout...)
+ if err != nil {
+ return ""
+ }
+ return res
+}
+
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// ----------------------------------MuxAPI implementation------------------------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+
+type muxAPI struct {
+ mux *serveMux
+ relativePath string
+ middleware Middleware
+}
+
+var (
+ // errAPIContextNotFound returns an error with message: 'From .API: "Context *iris.Context could not be found..'
+ errAPIContextNotFound = errors.New("From .API: Context *iris.Context could not be found.")
+ // errDirectoryFileNotFound returns an error with message: 'Directory or file %s couldn't found. Trace: +error trace'
+ errDirectoryFileNotFound = errors.New("Directory or file %s couldn't found. Trace: %s")
+)
+
+var _ MuxAPI = &muxAPI{}
+
+func pathIsSubdomain(s string) bool {
+ return strings.Index(s, subdomainIndicator) != -1
+}
+
+// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
+// Party can also be named as 'Join' or 'Node' or 'Group' , Party chosen because it has more fun
+func Party(relativePath string, handlersFn ...HandlerFunc) MuxAPI {
+ return Default.Party(relativePath, handlersFn...)
+}
+
+// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
+// Party can also be named as 'Join' or 'Node' or 'Group' , Party chosen because it has more fun
+func (api *muxAPI) Party(relativePath string, handlersFn ...HandlerFunc) MuxAPI {
+ parentPath := api.relativePath
+ dot := string(subdomainIndicator[0])
+ if len(parentPath) > 0 && parentPath[0] == slashByte && strings.HasSuffix(relativePath, dot) { // if ends with . , example: admin., it's subdomain->
+ parentPath = parentPath[1:] // remove first slash
+ }
+
+ fullpath := parentPath + relativePath
+ middleware := convertToHandlers(handlersFn)
+ // append the parent's +child's handlers
+ middleware = joinMiddleware(api.middleware, middleware)
+ return &muxAPI{relativePath: fullpath, mux: api.mux, middleware: middleware}
+}
+
+// Use registers a Handler middleware
+func Use(handlers ...Handler) {
+ Default.Use(handlers...)
+}
+
+// UseFunc registers a HandlerFunc middleware
+func UseFunc(handlersFn ...HandlerFunc) {
+ Default.UseFunc(handlersFn...)
+}
+
+// Use registers a Handler middleware
+func (api *muxAPI) Use(handlers ...Handler) {
+ api.middleware = append(api.middleware, handlers...)
+}
+
+// UseFunc registers a HandlerFunc middleware
+func (api *muxAPI) UseFunc(handlersFn ...HandlerFunc) {
+ api.Use(convertToHandlers(handlersFn)...)
+}
+
+// Handle registers a route to the server's router
+// if empty method is passed then registers handler(s) for all methods, same as .Any, but returns nil as result
+func Handle(method string, registedPath string, handlers ...Handler) RouteNameFunc {
+ return Default.Handle(method, registedPath, handlers...)
+}
+
+// HandleFunc registers and returns a route with a method string, path string and a handler
+// registedPath is the relative url path
+func HandleFunc(method string, registedPath string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return Default.HandleFunc(method, registedPath, handlersFn...)
+}
+
+// Handle registers a route to the server's router
+// if empty method is passed then registers handler(s) for all methods, same as .Any, but returns nil as result
+func (api *muxAPI) Handle(method string, registedPath string, handlers ...Handler) RouteNameFunc {
+ if method == "" { // then use like it was .Any
+ for _, k := range AllMethods {
+ api.Handle(k, registedPath, handlers...)
+ }
+ return nil
+ }
+
+ fullpath := api.relativePath + registedPath // keep the last "/" if any, "/xyz/"
+
+ middleware := joinMiddleware(api.middleware, handlers)
+
+ // here we separate the subdomain and relative path
+ subdomain := ""
+ path := fullpath
+
+ if dotWSlashIdx := strings.Index(path, subdomainIndicator); dotWSlashIdx > 0 {
+ subdomain = fullpath[0 : dotWSlashIdx+1] // admin.
+ path = fullpath[dotWSlashIdx+1:] // /
+ }
+
+ path = strings.Replace(path, "//", "/", -1) // fix the path if double //
+ return api.mux.register([]byte(method), subdomain, path, middleware).setName
+}
+
+// HandleFunc registers and returns a route with a method string, path string and a handler
+// registedPath is the relative url path
+func (api *muxAPI) HandleFunc(method string, registedPath string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return api.Handle(method, registedPath, convertToHandlers(handlersFn)...)
+}
+
+// H_ is used to convert a context.IContext handler func to iris.HandlerFunc, is used only inside iris internal package to avoid import cycles
+func (api *muxAPI) H_(method string, registedPath string, fn func(context.IContext)) func(string) {
+ return api.HandleFunc(method, registedPath, func(ctx *Context) {
+ fn(ctx)
+ })
+}
+
+// API converts & registers a custom struct to the router
+// receives two parameters
+// first is the request path (string)
+// second is the custom struct (interface{}) which can be anything that has a *iris.Context as field.
+// third is the common middlewares, it's optional
+//
+// Note that API's routes have their default-name to the full registed path,
+// no need to give a special name for it, because it's not supposed to be used inside your templates.
+//
+// Recommend to use when you retrieve data from an external database,
+// and the router-performance is not the (only) thing which slows the server's overall performance.
+//
+// This is a slow method, if you care about router-performance use the Handle/HandleFunc/Get/Post/Put/Delete/Trace/Options... instead
+func API(path string, restAPI HandlerAPI, middleware ...HandlerFunc) {
+ Default.API(path, restAPI, middleware...)
+}
+
+// API converts & registers a custom struct to the router
+// receives two parameters
+// first is the request path (string)
+// second is the custom struct (interface{}) which can be anything that has a *iris.Context as field.
+// third is the common middleware, it's optional
+//
+// Note that API's routes have their default-name to the full registed path,
+// no need to give a special name for it, because it's not supposed to be used inside your templates.
+//
+// Recommend to use when you retrieve data from an external database,
+// and the router-performance is not the (only) thing which slows the server's overall performance.
+//
+// This is a slow method, if you care about router-performance use the Handle/HandleFunc/Get/Post/Put/Delete/Trace/Options... instead
+func (api *muxAPI) API(path string, restAPI HandlerAPI, middleware ...HandlerFunc) {
+ // here we need to find the registed methods and convert them to handler funcs
+ // methods are collected by method naming: Get(),GetBy(...), Post(),PostBy(...), Put() and so on
+
+ typ := reflect.ValueOf(restAPI).Type()
+ contextField, found := typ.FieldByName("Context")
+ if !found {
+ panic(errAPIContextNotFound.Return())
+ }
+
+ // check & register the Get(),Post(),Put(),Delete() and so on
+ for _, methodName := range AllMethods {
+
+ methodCapitalName := strings.Title(strings.ToLower(methodName))
+
+ if method, found := typ.MethodByName(methodCapitalName); found {
+ methodFunc := method.Func
+ if !methodFunc.IsValid() || methodFunc.Type().NumIn() > 1 { // for any case
+ continue
+ }
+
+ func(path string, typ reflect.Type, contextField reflect.StructField, methodFunc reflect.Value, method string) {
+ var handlersFn []HandlerFunc
+
+ handlersFn = append(handlersFn, middleware...)
+ handlersFn = append(handlersFn, func(ctx *Context) {
+ newController := reflect.New(typ).Elem()
+ newController.FieldByName("Context").Set(reflect.ValueOf(ctx))
+ methodFunc.Call([]reflect.Value{newController})
+ })
+ // register route
+ api.HandleFunc(method, path, handlersFn...)
+ }(path, typ, contextField, methodFunc, methodName)
+
+ }
+
+ }
+
+ // check for GetBy/PostBy(id string, something_else string) , these must be requested by the same order.
+ // (we could do this in the same top loop but I don't want)
+ // GET, DELETE -> with url named parameters (/users/:id/:secondArgumentIfExists)
+ // POST, PUT -> with post values (form)
+ // all other with URL Parameters (?something=this&else=other
+ //
+ // or no, I changed my mind, let all be named parameters and let users to decide what info they need,
+ // using the Context to take more values (post form,url params and so on).-
+
+ for _, methodName := range AllMethods {
+ methodWithBy := strings.Title(strings.ToLower(methodName)) + "By"
+ if method, found := typ.MethodByName(methodWithBy); found {
+ methodFunc := method.Func
+ if !methodFunc.IsValid() || methodFunc.Type().NumIn() < 2 { //it's By but it has not receive any arguments so its not api's
+ continue
+ }
+ methodFuncType := methodFunc.Type()
+ numInLen := methodFuncType.NumIn() // how much data we should receive from the request
+ registedPath := path
+
+ for i := 1; i < numInLen; i++ { // from 1 because the first is the 'object'
+ if registedPath[len(registedPath)-1] == slashByte {
+ registedPath += ":param" + strconv.Itoa(i)
+ } else {
+ registedPath += "/:param" + strconv.Itoa(i)
+ }
+ }
+
+ func(registedPath string, typ reflect.Type, contextField reflect.StructField, methodFunc reflect.Value, paramsLen int, method string) {
+ var handlersFn []HandlerFunc
+
+ handlersFn = append(handlersFn, middleware...)
+ handlersFn = append(handlersFn, func(ctx *Context) {
+ newController := reflect.New(typ).Elem()
+ newController.FieldByName("Context").Set(reflect.ValueOf(ctx))
+ args := make([]reflect.Value, paramsLen+1, paramsLen+1)
+ args[0] = newController
+ for i := 0; i < paramsLen; i++ {
+ args[i+1] = reflect.ValueOf(ctx.Params[i].Value)
+ }
+ methodFunc.Call(args)
+ })
+ // register route
+ api.HandleFunc(method, registedPath, handlersFn...)
+ }(registedPath, typ, contextField, methodFunc, numInLen-1, methodName)
+
+ }
+
+ }
+
+}
+
+// Get registers a route for the Get http method
+func Get(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return Default.Get(path, handlersFn...)
+}
+
+// Post registers a route for the Post http method
+func Post(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return Default.Post(path, handlersFn...)
+}
+
+// Put registers a route for the Put http method
+func Put(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return Default.Put(path, handlersFn...)
+}
+
+// Delete registers a route for the Delete http method
+func Delete(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return Default.Delete(path, handlersFn...)
+}
+
+// Connect registers a route for the Connect http method
+func Connect(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return Default.Connect(path, handlersFn...)
+}
+
+// Head registers a route for the Head http method
+func Head(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return Default.Head(path, handlersFn...)
+}
+
+// Options registers a route for the Options http method
+func Options(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return Default.Options(path, handlersFn...)
+}
+
+// Patch registers a route for the Patch http method
+func Patch(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return Default.Patch(path, handlersFn...)
+}
+
+// Trace registers a route for the Trace http method
+func Trace(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return Default.Trace(path, handlersFn...)
+}
+
+// Any registers a route for ALL of the http methods (Get,Post,Put,Head,Patch,Options,Connect,Delete)
+func Any(registedPath string, handlersFn ...HandlerFunc) {
+ Default.Any(registedPath, handlersFn...)
+
+}
+
+// Get registers a route for the Get http method
+func (api *muxAPI) Get(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return api.HandleFunc(MethodGet, path, handlersFn...)
+}
+
+// Post registers a route for the Post http method
+func (api *muxAPI) Post(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return api.HandleFunc(MethodPost, path, handlersFn...)
+}
+
+// Put registers a route for the Put http method
+func (api *muxAPI) Put(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return api.HandleFunc(MethodPut, path, handlersFn...)
+}
+
+// Delete registers a route for the Delete http method
+func (api *muxAPI) Delete(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return api.HandleFunc(MethodDelete, path, handlersFn...)
+}
+
+// Connect registers a route for the Connect http method
+func (api *muxAPI) Connect(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return api.HandleFunc(MethodConnect, path, handlersFn...)
+}
+
+// Head registers a route for the Head http method
+func (api *muxAPI) Head(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return api.HandleFunc(MethodHead, path, handlersFn...)
+}
+
+// Options registers a route for the Options http method
+func (api *muxAPI) Options(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return api.HandleFunc(MethodOptions, path, handlersFn...)
+}
+
+// Patch registers a route for the Patch http method
+func (api *muxAPI) Patch(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return api.HandleFunc(MethodPatch, path, handlersFn...)
+}
+
+// Trace registers a route for the Trace http method
+func (api *muxAPI) Trace(path string, handlersFn ...HandlerFunc) RouteNameFunc {
+ return api.HandleFunc(MethodTrace, path, handlersFn...)
+}
+
+// Any registers a route for ALL of the http methods (Get,Post,Put,Head,Patch,Options,Connect,Delete)
+func (api *muxAPI) Any(registedPath string, handlersFn ...HandlerFunc) {
+ for _, k := range AllMethods {
+ api.HandleFunc(k, registedPath, handlersFn...)
}
}
-// Server returns the server
-func (s *Iris) Server() *server.Server {
- return s.server
+// StaticHandler returns a Handler to serve static system directory
+// Accepts 5 parameters
+//
+// first is the systemPath (string)
+// Path to the root directory to serve files from.
+//
+// second is the stripSlashes (int) level
+// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
+// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
+// * stripSlashes = 2, original path: "/foo/bar", result: ""
+//
+// third is the compress (bool)
+// Transparently compresses responses if set to true.
+//
+// The server tries minimizing CPU usage by caching compressed files.
+// It adds fasthttp.FSCompressedFileSuffix suffix to the original file name and
+// tries saving the resulting compressed file under the new file name.
+// So it is advisable to give the server write access to Root
+// and to all inner folders in order to minimze CPU usage when serving
+// compressed responses.
+//
+// fourth is the generateIndexPages (bool)
+// Index pages for directories without files matching IndexNames
+// are automatically generated if set.
+//
+// Directory index generation may be quite slow for directories
+// with many files (more than 1K), so it is discouraged enabling
+// index pages' generation for such directories.
+//
+// fifth is the indexNames ([]string)
+// List of index file names to try opening during directory access.
+//
+// For example:
+//
+// * index.html
+// * index.htm
+// * my-super-index.xml
+//
+func StaticHandler(systemPath string, stripSlashes int, compress bool, generateIndexPages bool, indexNames []string) HandlerFunc {
+ return Default.StaticHandler(systemPath, stripSlashes, compress, generateIndexPages, indexNames)
}
-// Plugins returns the plugin container
-func (s *Iris) Plugins() *PluginContainer {
- return s.plugins
+// StaticHandler returns a Handler to serve static system directory
+// Accepts 5 parameters
+//
+// first is the systemPath (string)
+// Path to the root directory to serve files from.
+//
+// second is the stripSlashes (int) level
+// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
+// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
+// * stripSlashes = 2, original path: "/foo/bar", result: ""
+//
+// third is the compress (bool)
+// Transparently compresses responses if set to true.
+//
+// The server tries minimizing CPU usage by caching compressed files.
+// It adds fasthttp.FSCompressedFileSuffix suffix to the original file name and
+// tries saving the resulting compressed file under the new file name.
+// So it is advisable to give the server write access to Root
+// and to all inner folders in order to minimze CPU usage when serving
+// compressed responses.
+//
+// fourth is the generateIndexPages (bool)
+// Index pages for directories without files matching IndexNames
+// are automatically generated if set.
+//
+// Directory index generation may be quite slow for directories
+// with many files (more than 1K), so it is discouraged enabling
+// index pages' generation for such directories.
+//
+// fifth is the indexNames ([]string)
+// List of index file names to try opening during directory access.
+//
+// For example:
+//
+// * index.html
+// * index.htm
+// * my-super-index.xml
+//
+func (api *muxAPI) StaticHandler(systemPath string, stripSlashes int, compress bool, generateIndexPages bool, indexNames []string) HandlerFunc {
+ if indexNames == nil {
+ indexNames = []string{}
+ }
+ fs := &fasthttp.FS{
+ // Path to directory to serve.
+ Root: systemPath,
+ IndexNames: indexNames,
+ // Generate index pages if client requests directory contents.
+ GenerateIndexPages: generateIndexPages,
+
+ // Enable transparent compression to save network traffic.
+ Compress: compress,
+ CacheDuration: config.StaticCacheDuration,
+ CompressedFileSuffix: config.CompressedFileSuffix,
+ }
+
+ if stripSlashes > 0 {
+ fs.PathRewrite = fasthttp.NewPathSlashesStripper(stripSlashes)
+ }
+
+ // Create request handler for serving static files.
+ h := fs.NewRequestHandler()
+ return HandlerFunc(func(ctx *Context) {
+ h(ctx.RequestCtx)
+ errCode := ctx.RequestCtx.Response.StatusCode()
+ if errCode == StatusNotFound || errCode == StatusBadRequest || errCode == StatusInternalServerError {
+ api.mux.fireError(errCode, ctx)
+ }
+ if ctx.pos < uint8(len(ctx.middleware))-1 {
+ ctx.Next() // for any case
+ }
+
+ })
}
-// Config returns the configs
-func (s *Iris) Config() *config.Iris {
- return s.config
+// Static registers a route which serves a system directory
+// this doesn't generates an index page which list all files
+// no compression is used also, for these features look at StaticFS func
+// accepts three parameters
+// first parameter is the request url path (string)
+// second parameter is the system directory (string)
+// third parameter is the level (int) of stripSlashes
+// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
+// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
+// * stripSlashes = 2, original path: "/foo/bar", result: ""
+func Static(relative string, systemPath string, stripSlashes int) RouteNameFunc {
+ return Default.Static(relative, systemPath, stripSlashes)
}
-// Logger returns the logger
-func (s *Iris) Logger() *logger.Logger {
- return s.logger
+// Static registers a route which serves a system directory
+// this doesn't generates an index page which list all files
+// no compression is used also, for these features look at StaticFS func
+// accepts three parameters
+// first parameter is the request url path (string)
+// second parameter is the system directory (string)
+// third parameter is the level (int) of stripSlashes
+// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
+// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
+// * stripSlashes = 2, original path: "/foo/bar", result: ""
+func (api *muxAPI) Static(relative string, systemPath string, stripSlashes int) RouteNameFunc {
+ if relative[len(relative)-1] != slashByte { // if / then /*filepath, if /something then /something/*filepath
+ relative += slash
+ }
+
+ h := api.StaticHandler(systemPath, stripSlashes, false, false, nil)
+
+ api.Head(relative+"*filepath", h)
+ return api.Get(relative+"*filepath", h)
}
-// Rest returns the rest render
-func (s *Iris) Rest() *rest.Render {
- return s.rest
+// StaticFS registers a route which serves a system directory
+// this is the fastest method to serve static files
+// generates an index page which list all files
+// if you use this method it will generate compressed files also
+// think this function as small fileserver with http
+// accepts three parameters
+// first parameter is the request url path (string)
+// second parameter is the system directory (string)
+// third parameter is the level (int) of stripSlashes
+// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
+// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
+// * stripSlashes = 2, original path: "/foo/bar", result: ""
+func StaticFS(reqPath string, systemPath string, stripSlashes int) RouteNameFunc {
+ return Default.StaticFS(reqPath, systemPath, stripSlashes)
}
-// Templates returns the template render
-func (s *Iris) Templates() *template.Template {
- s.initTemplates() // for any case the user called .Templates() before server's listen
- return s.templates
+// StaticFS registers a route which serves a system directory
+// this is the fastest method to serve static files
+// generates an index page which list all files
+// if you use this method it will generate compressed files also
+// think this function as small fileserver with http
+// accepts three parameters
+// first parameter is the request url path (string)
+// second parameter is the system directory (string)
+// third parameter is the level (int) of stripSlashes
+// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
+// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
+// * stripSlashes = 2, original path: "/foo/bar", result: ""
+func (api *muxAPI) StaticFS(reqPath string, systemPath string, stripSlashes int) RouteNameFunc {
+ if reqPath[len(reqPath)-1] != slashByte {
+ reqPath += "/"
+ }
+
+ h := api.StaticHandler(systemPath, stripSlashes, true, true, nil)
+ api.Head(reqPath+"*filepath", h)
+ return api.Get(reqPath+"*filepath", h)
}
-// Websocket returns the websocket server
-func (s *Iris) Websocket() websocket.Server {
- s.initWebsocketServer() // for any case the user called .Websocket() before server's listen
- return s.websocketServer
+// StaticWeb same as Static but if index.html exists and request uri is '/' then display the index.html's contents
+// accepts three parameters
+// first parameter is the request url path (string)
+// second parameter is the system directory (string)
+// third parameter is the level (int) of stripSlashes
+// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
+// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
+// * stripSlashes = 2, original path: "/foo/bar", result: ""
+// * if you don't know what to put on stripSlashes just 1
+func StaticWeb(reqPath string, systemPath string, stripSlashes int) RouteNameFunc {
+ return Default.StaticWeb(reqPath, systemPath, stripSlashes)
}
-// Mail returns the mail sender service
-func (s *Iris) Mail() mail.Service {
- s.initMailService()
- return s.mailService
+// StaticWeb same as Static but if index.html exists and request uri is '/' then display the index.html's contents
+// accepts three parameters
+// first parameter is the request url path (string)
+// second parameter is the system directory (string)
+// third parameter is the level (int) of stripSlashes
+// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
+// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
+// * stripSlashes = 2, original path: "/foo/bar", result: ""
+// * if you don't know what to put on stripSlashes just 1
+func (api *muxAPI) StaticWeb(reqPath string, systemPath string, stripSlashes int) RouteNameFunc {
+ if reqPath[len(reqPath)-1] != slashByte { // if / then /*filepath, if /something then /something/*filepath
+ reqPath += "/"
+ }
+
+ hasIndex := utils.Exists(systemPath + utils.PathSeparator + "index.html")
+ serveHandler := api.StaticHandler(systemPath, stripSlashes, false, !hasIndex, nil) // if not index.html exists then generate index.html which shows the list of files
+ indexHandler := func(ctx *Context) {
+ if len(ctx.Param("filepath")) < 2 && hasIndex {
+ ctx.Request.SetRequestURI("index.html")
+ }
+ ctx.Next()
+
+ }
+ api.Head(reqPath+"*filepath", indexHandler, serveHandler)
+ return api.Get(reqPath+"*filepath", indexHandler, serveHandler)
+}
+
+// StaticServe serves a directory as web resource
+// it's the simpliest form of the Static* functions
+// Almost same usage as StaticWeb
+// accepts only one required parameter which is the systemPath ( the same path will be used to register the GET&HEAD routes)
+// if second parameter is empty, otherwise the requestPath is the second parameter
+// it uses gzip compression (compression on each request, no file cache)
+func StaticServe(systemPath string, requestPath ...string) RouteNameFunc {
+ return Default.StaticServe(systemPath, requestPath...)
+}
+
+// StaticServe serves a directory as web resource
+// it's the simpliest form of the Static* functions
+// Almost same usage as StaticWeb
+// accepts only one required parameter which is the systemPath ( the same path will be used to register the GET&HEAD routes)
+// if second parameter is empty, otherwise the requestPath is the second parameter
+// it uses gzip compression (compression on each request, no file cache)
+func (api *muxAPI) StaticServe(systemPath string, requestPath ...string) RouteNameFunc {
+ var reqPath string
+
+ if len(requestPath) == 0 {
+ reqPath = strings.Replace(systemPath, utils.PathSeparator, slash, -1) // replaces any \ to /
+ reqPath = strings.Replace(reqPath, "//", slash, -1) // for any case, replaces // to /
+ reqPath = strings.Replace(reqPath, ".", "", -1) // replace any dots (./mypath -> /mypath)
+ } else {
+ reqPath = requestPath[0]
+ }
+
+ return api.Get(reqPath+"/*file", func(ctx *Context) {
+ filepath := ctx.Param("file")
+
+ spath := strings.Replace(filepath, "/", utils.PathSeparator, -1)
+ spath = path.Join(systemPath, spath)
+
+ if !utils.DirectoryExists(spath) {
+ ctx.NotFound()
+ return
+ }
+
+ ctx.ServeFile(spath, true)
+ })
+}
+
+// StaticContent serves bytes, memory cached, on the reqPath
+// a good example of this is how the websocket server uses that to auto-register the /iris-ws.js
+func StaticContent(reqPath string, contentType string, content []byte) RouteNameFunc {
+ return Default.StaticContent(reqPath, contentType, content)
+}
+
+// StaticContent serves bytes, memory cached, on the reqPath
+// 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) func(string) { // func(string) because we use that on websockets
+ modtime := time.Now()
+ modtimeStr := modtime.UTC().Format(config.TimeFormat)
+
+ h := func(ctx *Context) {
+ if t, err := time.Parse(config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && modtime.Before(t.Add(config.StaticCacheDuration)) {
+ ctx.Response.Header.Del(contentType)
+ ctx.Response.Header.Del(contentLength)
+ ctx.SetStatusCode(StatusNotModified)
+ return
+ }
+
+ ctx.Response.Header.Set(contentType, cType)
+ ctx.Response.Header.Set(lastModified, modtimeStr)
+ ctx.SetStatusCode(StatusOK)
+ ctx.Response.SetBody(content)
+ }
+ api.Head(reqPath, h)
+ return api.Get(reqPath, h)
+}
+
+// Favicon serves static favicon
+// accepts 2 parameters, second is optional
+// favPath (string), declare the system directory path of the __.ico
+// requestPath (string), it's the route's path, by default this is the "/favicon.ico" because some browsers tries to get this by default first,
+// you can declare your own path if you have more than one favicon (desktop, mobile and so on)
+//
+// this func will add a route for you which will static serve the /yuorpath/yourfile.ico to the /yourfile.ico (nothing special that you can't handle by yourself)
+// Note that you have to call it on every favicon you have to serve automatically (dekstop, mobile and so on)
+//
+// panics on error
+func Favicon(favPath string, requestPath ...string) RouteNameFunc {
+ return Default.Favicon(favPath, requestPath...)
+}
+
+// Favicon serves static favicon
+// accepts 2 parameters, second is optional
+// favPath (string), declare the system directory path of the __.ico
+// requestPath (string), it's the route's path, by default this is the "/favicon.ico" because some browsers tries to get this by default first,
+// you can declare your own path if you have more than one favicon (desktop, mobile and so on)
+//
+// this func will add a route for you which will static serve the /yuorpath/yourfile.ico to the /yourfile.ico (nothing special that you can't handle by yourself)
+// Note that you have to call it on every favicon you have to serve automatically (dekstop, mobile and so on)
+//
+// panics on error
+func (api *muxAPI) Favicon(favPath string, requestPath ...string) RouteNameFunc {
+ f, err := os.Open(favPath)
+ if err != nil {
+ panic(errDirectoryFileNotFound.Format(favPath, err.Error()))
+ }
+ defer f.Close()
+ fi, _ := f.Stat()
+ if fi.IsDir() { // if it's dir the try to get the favicon.ico
+ fav := path.Join(favPath, "favicon.ico")
+ f, err = os.Open(fav)
+ if err != nil {
+ //we try again with .png
+ return api.Favicon(path.Join(favPath, "favicon.png"))
+ }
+ favPath = fav
+ fi, _ = f.Stat()
+ }
+ modtime := fi.ModTime().UTC().Format(config.TimeFormat)
+ cType := utils.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()))
+ }
+
+ h := func(ctx *Context) {
+ if t, err := time.Parse(config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && fi.ModTime().Before(t.Add(config.StaticCacheDuration)) {
+ ctx.Response.Header.Del(contentType)
+ ctx.Response.Header.Del(contentLength)
+ ctx.SetStatusCode(StatusNotModified)
+ return
+ }
+
+ ctx.Response.Header.Set(contentType, cType)
+ ctx.Response.Header.Set(lastModified, modtime)
+ ctx.SetStatusCode(StatusOK)
+ ctx.Response.SetBody(cacheFav)
+ }
+
+ reqPath := "/favicon" + path.Ext(fi.Name()) //we could use the filename, but because standards is /favicon.ico/.png.
+ if len(requestPath) > 0 {
+ reqPath = requestPath[0]
+ }
+
+ api.Head(reqPath, h)
+ return api.Get(reqPath, h)
}
diff --git a/iris_singleton.go b/iris_singleton.go
deleted file mode 100644
index 4622d6bf..00000000
--- a/iris_singleton.go
+++ /dev/null
@@ -1,412 +0,0 @@
-package iris
-
-import (
- "github.com/kataras/iris/config"
- "github.com/kataras/iris/logger"
- "github.com/kataras/iris/mail"
- "github.com/kataras/iris/render/rest"
- "github.com/kataras/iris/render/template"
- "github.com/kataras/iris/server"
- "github.com/kataras/iris/websocket"
-)
-
-// DefaultIris in order to use iris.Get(...,...) we need a default Iris on the package level
-var DefaultIris = New()
-
-// Listen starts the standalone http server
-// which listens to the addr parameter which as the form of
-// host:port or just port
-//
-// It panics on error if you need a func to return an error use the ListenWithErr
-// ex: iris.Listen(":8080")
-func Listen(addr string) {
- DefaultIris.Listen(addr)
-}
-
-// ListenWithErr starts the standalone http server
-// which listens to the addr parameter which as the form of
-// host:port or just port
-//
-// It returns an error you are responsible how to handle this
-// if you need a func to panic on error use the Listen
-// ex: log.Fatal(iris.ListenWithErr(":8080"))
-func ListenWithErr(addr string) error {
- return DefaultIris.ListenWithErr(addr)
-}
-
-// ListenTLS Starts a https server with certificates,
-// if you use this method the requests of the form of 'http://' will fail
-// only https:// connections are allowed
-// which listens to the addr parameter which as the form of
-// host:port or just port
-//
-// It panics on error if you need a func to return an error use the ListenTLSWithErr
-// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key")
-func ListenTLS(addr string, certFile, keyFile string) {
- DefaultIris.ListenTLS(addr, certFile, keyFile)
-}
-
-// ListenTLSWithErr Starts a https server with certificates,
-// if you use this method the requests of the form of 'http://' will fail
-// only https:// connections are allowed
-// which listens to the addr parameter which as the form of
-// host:port or just port
-//
-// It returns an error you are responsible how to handle this
-// if you need a func to panic on error use the ListenTLS
-// ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key"))
-func ListenTLSWithErr(addr string, certFile, keyFile string) error {
- return DefaultIris.ListenTLSWithErr(addr, certFile, keyFile)
-}
-
-// Close is used to close the net.Listener of the standalone http server which has already running via .Listen
-func Close() { DefaultIris.Close() }
-
-// Router implementation
-
-// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
-// Party can also be named as 'Join' or 'Node' or 'Group' , Party chosen because it has more fun
-func Party(path string, handlersFn ...HandlerFunc) IParty {
- return DefaultIris.Party(path, handlersFn...)
-}
-
-// Handle registers a route to the server's router
-// if empty method is passed then registers handler(s) for all methods, same as .Any
-func Handle(method string, registedPath string, handlers ...Handler) IRoute {
- return DefaultIris.Handle(method, registedPath, handlers...)
-}
-
-// HandleFunc registers a route with a method, path string, and a handler
-func HandleFunc(method string, path string, handlersFn ...HandlerFunc) IRoute {
- return DefaultIris.HandleFunc(method, path, handlersFn...)
-}
-
-// Wildcard same as .Party("*.")
-// registers a route for Dynamic subdomain
-// receives three parameters
-// the first is the http method
-// the second is the request path, can be a dynamic path also like others
-// the third are the handlerfuncs
-//
-// example: subdomains_2
-func Wildcard(method string, registedPath string, handlersFn ...HandlerFunc) {
- DefaultIris.Wildcard(method, registedPath, handlersFn...)
-}
-
-// API converts & registers a custom struct to the router
-// receives two parameters
-// first is the request path (string)
-// second is the custom struct (interface{}) which can be anything that has a *iris.Context as field.
-// third are the common middlewares, is optional parameter
-//
-// Note that API's routes have their default-name to the full registed path,
-// no need to give a special name for it, because it's not supposed to be used inside your templates.
-//
-// Recommend to use when you retrieve data from an external database,
-// and the router-performance is not the (only) thing which slows the server's overall performance.
-//
-// This is a slow method, if you care about router-performance use the Handle/HandleFunc/Get/Post/Put/Delete/Trace/Options... instead
-//
-// Usage:
-// All the below methods are optional except the *iris.Context field,
-// example with /users :
-/*
-
-package main
-
-import (
- "github.com/kataras/iris"
-)
-
-type UserAPI struct {
- *iris.Context
-}
-
-// GET /users
-func (u UserAPI) Get() {
- u.Write("Get from /users")
- // u.JSON(iris.StatusOK,myDb.AllUsers())
-}
-
-// GET /:param1 which its value passed to the id argument
-func (u UserAPI) GetBy(id string) { // id equals to u.Param("param1")
- u.Write("Get from /users/%s", id)
- // u.JSON(iris.StatusOK, myDb.GetUserById(id))
-
-}
-
-// PUT /users
-func (u UserAPI) Put() {
- name := u.FormValue("name")
- // myDb.InsertUser(...)
- println(string(name))
- println("Put from /users")
-}
-
-// POST /users/:param1
-func (u UserAPI) PostBy(id string) {
- name := u.FormValue("name") // you can still use the whole Context's features!
- // myDb.UpdateUser(...)
- println(string(name))
- println("Post from /users/" + id)
-}
-
-// DELETE /users/:param1
-func (u UserAPI) DeleteBy(id string) {
- // myDb.DeleteUser(id)
- println("Delete from /" + id)
-}
-
-func main() {
- iris.API("/users", UserAPI{})
- iris.Listen(":80")
-}
-*/
-func API(registedPath string, controller HandlerAPI, middlewares ...HandlerFunc) error {
- return DefaultIris.API(registedPath, controller, middlewares...)
-}
-
-// Use appends a middleware to the route or to the router if it's called from router
-func Use(handlers ...Handler) {
- DefaultIris.Use(handlers...)
-}
-
-// UseFunc same as Use but it accepts/receives ...HandlerFunc instead of ...Handler
-// form of acceptable: func(c *iris.Context){//first middleware}, func(c *iris.Context){//second middleware}
-func UseFunc(handlersFn ...HandlerFunc) {
- DefaultIris.UseFunc(handlersFn...)
-}
-
-// Get registers a route for the Get http method
-func Get(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return DefaultIris.Get(path, handlersFn...)
-}
-
-// Post registers a route for the Post http method
-func Post(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return DefaultIris.Post(path, handlersFn...)
-}
-
-// Put registers a route for the Put http method
-func Put(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return DefaultIris.Put(path, handlersFn...)
-}
-
-// Delete registers a route for the Delete http method
-func Delete(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return DefaultIris.Delete(path, handlersFn...)
-}
-
-// Connect registers a route for the Connect http method
-func Connect(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return DefaultIris.Connect(path, handlersFn...)
-}
-
-// Head registers a route for the Head http method
-func Head(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return DefaultIris.Head(path, handlersFn...)
-}
-
-// Options registers a route for the Options http method
-func Options(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return DefaultIris.Options(path, handlersFn...)
-}
-
-// Patch registers a route for the Patch http method
-func Patch(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return DefaultIris.Patch(path, handlersFn...)
-}
-
-// Trace registers a route for the Trace http methodd
-func Trace(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return DefaultIris.Trace(path, handlersFn...)
-}
-
-// Any registers a route for ALL of the http methods (Get,Post,Put,Head,Patch,Options,Connect,Delete)
-func Any(path string, handlersFn ...HandlerFunc) []IRoute {
- return DefaultIris.Any(path, handlersFn...)
-}
-
-// RouteByName returns a route by its name,if not found then returns a route with empty path
-// Note that the searching is case-sensitive
-func RouteByName(lookUpName string) IRoute {
- return DefaultIris.RouteByName(lookUpName)
-}
-
-// StaticHandlerFunc returns a HandlerFunc to serve static system directory
-// Accepts 5 parameters
-//
-// first is the systemPath (string)
-// Path to the root directory to serve files from.
-//
-// second is the stripSlashes (int) level
-// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
-// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
-// * stripSlashes = 2, original path: "/foo/bar", result: ""
-//
-// third is the compress (bool)
-// Transparently compresses responses if set to true.
-//
-// The server tries minimizing CPU usage by caching compressed files.
-// It adds FSCompressedFileSuffix suffix to the original file name and
-// tries saving the resulting compressed file under the new file name.
-// So it is advisable to give the server write access to Root
-// and to all inner folders in order to minimze CPU usage when serving
-// compressed responses.
-//
-// fourth is the generateIndexPages (bool)
-// Index pages for directories without files matching IndexNames
-// are automatically generated if set.
-//
-// Directory index generation may be quite slow for directories
-// with many files (more than 1K), so it is discouraged enabling
-// index pages' generation for such directories.
-//
-// fifth is the indexNames ([]string)
-// List of index file names to try opening during directory access.
-//
-// For example:
-//
-// * index.html
-// * index.htm
-// * my-super-index.xml
-//
-func StaticHandlerFunc(systemPath string, stripSlashes int, compress bool, generateIndexPages bool, indexNames []string) HandlerFunc {
- return DefaultIris.StaticHandlerFunc(systemPath, stripSlashes, compress, generateIndexPages, indexNames)
-}
-
-// Static registers a route which serves a system directory
-// this doesn't generates an index page which list all files
-// no compression is used also, for these features look at StaticFS func
-// accepts three parameters
-// first parameter is the request url path (string)
-// second parameter is the system directory (string)
-// third parameter is the level (int) of stripSlashes
-// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
-// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
-// * stripSlashes = 2, original path: "/foo/bar", result: ""
-func Static(reqPath string, systemPath string, stripSlashes int) {
- DefaultIris.Static(reqPath, systemPath, stripSlashes)
-}
-
-// StaticFS registers a route which serves a system directory
-// generates an index page which list all files
-// uses compression which file cache, if you use this method it will generate compressed files also
-// think this function as small fileserver with http
-// accepts three parameters
-// first parameter is the request url path (string)
-// second parameter is the system directory (string)
-// third parameter is the level (int) of stripSlashes
-// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
-// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
-// * stripSlashes = 2, original path: "/foo/bar", result: ""
-func StaticFS(reqPath string, systemPath string, stripSlashes int) {
- DefaultIris.StaticFS(reqPath, systemPath, stripSlashes)
-}
-
-// StaticWeb same as Static but if index.html exists and request uri is '/' then display the index.html's contents
-// accepts three parameters
-// first parameter is the request url path (string)
-// second parameter is the system directory (string)
-// third parameter is the level (int) of stripSlashes
-// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
-// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
-// * stripSlashes = 2, original path: "/foo/bar", result: ""
-func StaticWeb(reqPath string, systemPath string, stripSlashes int) {
- DefaultIris.StaticWeb(reqPath, systemPath, stripSlashes)
-}
-
-// StaticServe serves a directory as web resource
-// it's the simpliest form of the Static* functions
-// Almost same usage as StaticWeb
-// accepts only one required parameter which is the systemPath ( the same path will be used to register the GET&HEAD routes)
-// if second parameter is empty, otherwise the requestPath is the second parameter
-// it uses gzip compression (compression on each request, no file cache)
-func StaticServe(systemPath string, requestPath ...string) {
- DefaultIris.StaticServe(systemPath, requestPath...)
-}
-
-// Favicon serves static favicon
-// accepts 2 parameters, second is optionally
-// favPath (string), declare the system directory path of the __.ico
-// requestPath (string), it's the route's path, by default this is the "/favicon.ico" because some browsers tries to get this by default first,
-// you can declare your own path if you have more than one favicon (desktop, mobile and so on)
-//
-// this func will add a route for you which will static serve the /yuorpath/yourfile.ico to the /yourfile.ico (nothing special that you can't handle by yourself)
-// Note that you have to call it on every favicon you have to serve automatically (dekstop, mobile and so on)
-//
-// returns an error if something goes bad
-func Favicon(favPath string, requestPath ...string) error {
- return DefaultIris.Favicon(favPath)
-}
-
-// StaticContent serves bytes, memory cached, on the reqPath
-func StaticContent(reqPath string, contentType string, content []byte) {
- DefaultIris.StaticContent(reqPath, contentType, content)
-}
-
-// OnError Registers a handler for a specific http error status
-func OnError(httpStatus int, handler HandlerFunc) {
- DefaultIris.OnError(httpStatus, handler)
-}
-
-// EmitError executes the handler of the given error http status code
-func EmitError(httpStatus int, ctx *Context) {
- DefaultIris.EmitError(httpStatus, ctx)
-}
-
-// OnNotFound sets the handler for http status 404,
-// default is a response with text: 'Not Found' and status: 404
-func OnNotFound(handlerFunc HandlerFunc) {
- DefaultIris.OnNotFound(handlerFunc)
-}
-
-// OnPanic sets the handler for http status 500,
-// default is a response with text: The server encountered an unexpected condition which prevented it from fulfilling the request. and status: 500
-func OnPanic(handlerFunc HandlerFunc) {
- DefaultIris.OnPanic(handlerFunc)
-}
-
-// ***********************
-// Export DefaultIris's exported properties
-// ***********************
-
-// Server returns the server
-func Server() *server.Server {
- return DefaultIris.Server()
-}
-
-// Plugins returns the plugin container
-func Plugins() *PluginContainer {
- return DefaultIris.Plugins()
-}
-
-// Config returns the configs
-func Config() *config.Iris {
- return DefaultIris.Config()
-}
-
-// Logger returns the logger
-func Logger() *logger.Logger {
- return DefaultIris.Logger()
-}
-
-// Rest returns the rest render
-func Rest() *rest.Render {
- return DefaultIris.Rest()
-}
-
-// Templates returns the template render
-func Templates() *template.Template {
- return DefaultIris.Templates()
-}
-
-// Websocket returns the websocket server
-func Websocket() websocket.Server {
- return DefaultIris.Websocket()
-}
-
-// Mail returns the mail sender service
-func Mail() mail.Service {
- return DefaultIris.Mail()
-}
diff --git a/logger/logger.go b/logger/logger.go
index 931a0661..e23a02e5 100644
--- a/logger/logger.go
+++ b/logger/logger.go
@@ -17,7 +17,7 @@ var (
// Logger the logger
type Logger struct {
- config config.Logger
+ config *config.Logger
underline *color.Color
}
@@ -30,7 +30,7 @@ func attr(sgr int) color.Attribute {
func New(c config.Logger) *Logger {
color.Output = colorable.NewColorable(c.Out)
- l := &Logger{c, color.New(attr(c.ColorBgDefault), attr(c.ColorFgDefault), color.Bold)}
+ l := &Logger{&c, color.New(attr(c.ColorBgDefault), attr(c.ColorFgDefault), color.Bold)}
return l
}
@@ -47,7 +47,7 @@ func (l *Logger) IsEnabled() bool {
// ResetColors sets the colors to the default
// this func is called every time a success, info, warning, or danger message is printed
func (l *Logger) ResetColors() {
- l.underline.Add(attr(l.config.ColorBgDefault), attr(l.config.ColorFgBanner), color.Bold)
+ l.underline.Add(attr(l.config.ColorBgDefault), attr(l.config.ColorFgDefault))
}
// PrintBanner prints a text (banner) with BannerFgColor, BannerBgColor and a success message at the end
@@ -83,6 +83,7 @@ func (l *Logger) Printf(format string, a ...interface{}) {
// Arguments are handled in the manner of fmt.Print.
func (l *Logger) Print(a interface{}) {
if !l.config.Disabled {
+ l.ResetColors()
l.Printf("%#v", a)
}
}
@@ -125,7 +126,6 @@ func (l *Logger) Sucessf(format string, a ...interface{}) {
if !l.config.Disabled {
l.underline.Add(attr(l.config.ColorBgSuccess), attr(l.config.ColorFgSuccess))
l.Printf(format, a...)
- l.ResetColors()
}
}
@@ -135,7 +135,6 @@ func (l *Logger) Infof(format string, a ...interface{}) {
if !l.config.Disabled {
l.underline.Add(attr(l.config.ColorBgInfo), attr(l.config.ColorFgInfo))
l.Printf(format, a...)
- l.ResetColors()
}
}
@@ -145,7 +144,6 @@ func (l *Logger) Warningf(format string, a ...interface{}) {
if !l.config.Disabled {
l.underline.Add(attr(l.config.ColorBgWarning), attr(l.config.ColorFgWarning))
l.Printf(format, a...)
- l.ResetColors()
}
}
@@ -155,7 +153,6 @@ func (l *Logger) Dangerf(format string, a ...interface{}) {
if !l.config.Disabled {
l.underline.Add(attr(l.config.ColorBgDanger), attr(l.config.ColorFgDanger))
l.Printf(format, a...)
- l.ResetColors()
}
}
@@ -165,6 +162,5 @@ func (l *Logger) Otherf(format string, a ...interface{}) {
if !l.config.Disabled {
l.underline.Add(attr(l.config.ColorBgOther), attr(l.config.ColorFgOther))
l.Printf(format, a...)
- l.ResetColors()
}
}
diff --git a/mail/service.go b/mail/service.go
index 6cdbada1..703ce07d 100644
--- a/mail/service.go
+++ b/mail/service.go
@@ -6,23 +6,26 @@ import (
"net/mail"
"net/smtp"
"strings"
+ "sync"
"github.com/kataras/iris/config"
"github.com/kataras/iris/utils"
)
var buf = utils.NewBufferPool(64)
+var once sync.Once
type (
// Service is the interface which mail sender should implement
Service interface {
// Send sends a mail to recipients
// the body can be html also
- Send(to []string, subject, body string) error
+ Send(string, string, ...string) error
+ UpdateConfig(config.Mail)
}
mailer struct {
- config config.Mail
+ config *config.Mail
fromAddr mail.Address
auth smtp.Auth
authenticated bool
@@ -31,44 +34,46 @@ type (
// New creates and returns a new Service
func New(cfg config.Mail) Service {
- m := &mailer{config: cfg}
-
+ m := &mailer{config: &cfg}
if cfg.FromAlias == "" {
if !cfg.UseCommand && cfg.Username != "" && strings.Contains(cfg.Username, "@") {
- m.fromAddr = mail.Address{cfg.Username[0:strings.IndexByte(cfg.Username, '@')], cfg.Username}
+ m.fromAddr = mail.Address{Name: cfg.Username[0:strings.IndexByte(cfg.Username, '@')], Address: cfg.Username}
}
} else {
- m.fromAddr = mail.Address{cfg.FromAlias, cfg.Username}
+ m.fromAddr = mail.Address{Name: cfg.FromAlias, Address: cfg.Username}
}
-
return m
}
+func (m *mailer) UpdateConfig(cfg config.Mail) {
+ m.config = &cfg
+}
+
// Send sends a mail to recipients
// the body can be html also
-func (m *mailer) Send(to []string, subject, body string) error {
+func (m *mailer) Send(subject string, body string, to ...string) error {
if m.config.UseCommand {
- return m.sendCmd(to, subject, body)
+ return m.sendCmd(subject, body, to)
}
- return m.sendSMTP(to, subject, body)
+ return m.sendSMTP(subject, body, to)
}
-func (m *mailer) sendSMTP(to []string, subject, body string) error {
+func (m *mailer) sendSMTP(subject string, body string, to []string) error {
buffer := buf.Get()
defer buf.Put(buffer)
if !m.authenticated {
- if m.config.Username == "" || m.config.Password == "" || m.config.Host == "" || m.config.Port <= 0 {
+ cfg := m.config
+ if cfg.Username == "" || cfg.Password == "" || cfg.Host == "" || cfg.Port <= 0 {
return fmt.Errorf("Username, Password, Host & Port cannot be empty when using SMTP!")
}
- m.auth = smtp.PlainAuth("", m.config.Username, m.config.Password, m.config.Host)
+ m.auth = smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host)
m.authenticated = true
}
fullhost := fmt.Sprintf("%s:%d", m.config.Host, m.config.Port)
- /* START: This one helped me https://gist.github.com/andelf/5004821 */
header := make(map[string]string)
header["From"] = m.fromAddr.String()
header["To"] = strings.Join(to, ",")
@@ -83,8 +88,6 @@ func (m *mailer) sendSMTP(to []string, subject, body string) error {
}
message += "\r\n" + base64.StdEncoding.EncodeToString([]byte(body))
- /* END */
-
return smtp.SendMail(
fmt.Sprintf(fullhost),
m.auth,
@@ -94,14 +97,27 @@ func (m *mailer) sendSMTP(to []string, subject, body string) error {
)
}
-func (m *mailer) sendCmd(to []string, subject, body string) error {
+func (m *mailer) sendCmd(subject string, body string, to []string) error {
buffer := buf.Get()
defer buf.Put(buffer)
- cmd := utils.CommandBuilder("mail", "-s", subject, strings.Join(to, ","))
- cmd.AppendArguments("-a", "Content-type: text/html") //always html on
+ header := make(map[string]string)
+ header["To"] = strings.Join(to, ",")
+ header["Subject"] = subject
+ header["MIME-Version"] = "1.0"
+ header["Content-Type"] = "text/html; charset=\"utf-8\""
+ header["Content-Transfer-Encoding"] = "base64"
+
+ message := ""
+ for k, v := range header {
+ message += fmt.Sprintf("%s: %s\r\n", k, v)
+ }
+ message += "\r\n" + base64.StdEncoding.EncodeToString([]byte(body))
+ buffer.WriteString(message)
+ // fix by @qskousen
+ cmd := utils.CommandBuilder("sendmail", "-F", m.fromAddr.Name, "-f", m.fromAddr.Address, "-t")
cmd.Stdin = buffer
- _, err := cmd.Output()
+ _, err := cmd.CombinedOutput()
return err
}
diff --git a/middleware/basicauth/basicauth.go b/middleware/basicauth/basicauth.go
index a366832a..f4b4dbc4 100644
--- a/middleware/basicauth/basicauth.go
+++ b/middleware/basicauth/basicauth.go
@@ -143,7 +143,7 @@ func (b *basicAuthMiddleware) Serve(ctx *iris.Context) {
auth.logged = true
}
- if time.Now().Before(auth.expires) {
+ if time.Now().After(auth.expires) {
b.askForCredentials(ctx) // ask for authentication again
return
}
diff --git a/middleware/logger/logger.go b/middleware/logger/logger.go
index 7a3daf4d..5fa5f613 100644
--- a/middleware/logger/logger.go
+++ b/middleware/logger/logger.go
@@ -11,11 +11,9 @@ import (
// Options are the options of the logger middlweare
// contains 5 bools
-// Latency, Status, IP, Method, Path
+// Status, IP, Method, Path, EnableColors
// if set to true then these will print
type Options struct {
- // Latency displays latency (bool)
- Latency bool
// Status displays status code (bool)
Status bool
// IP displays request's remote address (bool)
@@ -24,11 +22,13 @@ type Options struct {
Method bool
// Path displays the request path (bool)
Path bool
+ // EnableColors defaults to false
+ EnableColors bool
}
// DefaultOptions returns an options which all properties are true
func DefaultOptions() Options {
- return Options{true, true, true, true, true}
+ return Options{true, true, true, true, false}
}
type loggerMiddleware struct {
@@ -45,17 +45,13 @@ func (l *loggerMiddleware) Serve(ctx *iris.Context) {
path = ctx.PathString()
method = ctx.MethodString()
- if l.options.Latency {
- startTime = time.Now()
- }
+ startTime = time.Now()
ctx.Next()
- if l.options.Latency {
- //no time.Since in order to format it well after
- endTime = time.Now()
- date = endTime.Format("01/02 - 15:04:05")
- latency = endTime.Sub(startTime)
- }
+ //no time.Since in order to format it well after
+ endTime = time.Now()
+ date = endTime.Format("01/02 - 15:04:05")
+ latency = endTime.Sub(startTime)
if l.options.Status {
status = strconv.Itoa(ctx.Response.StatusCode())
@@ -74,14 +70,18 @@ func (l *loggerMiddleware) Serve(ctx *iris.Context) {
}
//finally print the logs
- if l.options.Latency {
- l.Otherf("%s %v %4v %s %s %s \n", date, status, latency, ip, method, path)
- } else {
- l.Otherf("%s %v %s %s %s \n", date, status, ip, method, path)
- }
+ l.printf("%s %v %4v %s %s %s \n", date, status, latency, ip, method, path)
}
+func (l *loggerMiddleware) printf(format string, a ...interface{}) {
+ if l.options.EnableColors {
+ l.Logger.Otherf(format, a...)
+ } else {
+ l.Logger.Printf(format, a...)
+ }
+}
+
// Default returns the logger middleware as Handler with the default settings
func New(theLogger *logger.Logger, options ...Options) iris.HandlerFunc {
if theLogger == nil {
diff --git a/npm/npm.go b/npm/npm.go
deleted file mode 100644
index 6ec2beef..00000000
--- a/npm/npm.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package npm
-
-import (
- "fmt"
- "strings"
- "time"
-
- "github.com/kataras/iris/utils"
-)
-
-var (
- // NodeModules is the path of the root npm modules
- // Ex: C:\\Users\\kataras\\AppData\\Roaming\\npm\\node_modules
- NodeModules string
-)
-
-type (
- // Result holds Message and Error, if error != nil then the npm command has failed
- Result struct {
- // Message the message (string)
- Message string
- // Error the error (if any)
- Error error
- }
-)
-
-// init sets the root directory for the node_modules
-func init() {
- NodeModules = utils.MustCommand("npm", "root", "-g") //here it ends with \n we have to remove it
- NodeModules = NodeModules[0 : len(NodeModules)-1]
-}
-
-func success(output string, a ...interface{}) Result {
- return Result{fmt.Sprintf(output, a...), nil}
-}
-
-func fail(errMsg string, a ...interface{}) Result {
- return Result{"", fmt.Errorf("\n"+errMsg, a...)}
-}
-
-// Output returns the error message if result.Error exists, otherwise returns the result.Message
-func (res Result) Output() (out string) {
- if res.Error != nil {
- out = res.Error.Error()
- } else {
- out = res.Message
- }
- return
-}
-
-// Install installs a module
-func Install(moduleName string) Result {
- finish := make(chan bool)
-
- go func() {
- print("\n|")
- print("_")
- print("|")
-
- for {
- select {
- case v := <-finish:
- {
- if v {
- print("\010\010\010") //remove the loading chars
- close(finish)
- return
- }
-
- }
- default:
- print("\010\010-")
- time.Sleep(time.Second / 2)
- print("\010\\")
- time.Sleep(time.Second / 2)
- print("\010|")
- time.Sleep(time.Second / 2)
- print("\010/")
- time.Sleep(time.Second / 2)
- print("\010-")
- time.Sleep(time.Second / 2)
- print("|")
- }
- }
-
- }()
- out, err := utils.Command("npm", "install", moduleName, "-g")
- finish <- true
- if err != nil {
- return fail("Error installing module %s. Trace: %s", moduleName, err.Error())
- }
-
- return success("\n%s installed %s", moduleName, out)
-
-}
-
-// Unistall removes a module
-func Unistall(moduleName string) Result {
- out, err := utils.Command("npm", "unistall", "-g", moduleName)
- if err != nil {
- return fail("Error unstalling module %s. Trace: %s", moduleName, err.Error())
- }
- return success("\n %s unistalled %s", moduleName, out)
-
-}
-
-// Abs returns the absolute path of the global node_modules directory + relative
-func Abs(relativePath string) string {
- return NodeModules + utils.PathSeparator + strings.Replace(relativePath, "/", utils.PathSeparator, -1)
-}
-
-// Exists returns true if a module exists
-// here we have two options
-//1 . search by command something like npm -ls -g --depth=x
-//2. search on files, we choose the second
-func Exists(executableRelativePath string) bool {
- execAbsPath := Abs(executableRelativePath)
- if execAbsPath == "" {
- return false
- }
-
- return utils.Exists(execAbsPath)
-}
diff --git a/party.go b/party.go
deleted file mode 100644
index bbdb1c27..00000000
--- a/party.go
+++ /dev/null
@@ -1,642 +0,0 @@
-package iris
-
-import (
- "path"
- "reflect"
- "strconv"
- "strings"
-
- "os"
-
- "time"
-
- "github.com/kataras/iris/config"
- "github.com/kataras/iris/context"
- "github.com/kataras/iris/utils"
- "github.com/valyala/fasthttp"
-)
-
-type (
- // IParty is the interface which implements the whole Party of routes
- IParty interface {
- Handle(string, string, ...Handler) IRoute
- HandleFunc(string, string, ...HandlerFunc) IRoute
- Wildcard(string, string, ...HandlerFunc)
- API(path string, controller HandlerAPI, middlewares ...HandlerFunc) error
- Get(string, ...HandlerFunc) RouteNameFunc
- Post(string, ...HandlerFunc) RouteNameFunc
- Put(string, ...HandlerFunc) RouteNameFunc
- Delete(string, ...HandlerFunc) RouteNameFunc
- Connect(string, ...HandlerFunc) RouteNameFunc
- Head(string, ...HandlerFunc) RouteNameFunc
- Options(string, ...HandlerFunc) RouteNameFunc
- Patch(string, ...HandlerFunc) RouteNameFunc
- Trace(string, ...HandlerFunc) RouteNameFunc
- Any(string, ...HandlerFunc) []IRoute
- Use(...Handler)
- UseFunc(...HandlerFunc)
- StaticHandlerFunc(systemPath string, stripSlashes int, compress bool, generateIndexPages bool, indexNames []string) HandlerFunc
- Static(string, string, int)
- StaticFS(string, string, int)
- StaticWeb(relative string, systemPath string, stripSlashes int)
- StaticServe(systemPath string, requestPath ...string)
- Party(string, ...HandlerFunc) IParty // Each party can have a party too
- IsRoot() bool
- }
-
- // GardenParty is the struct which makes all the job for registering routes and middlewares
- GardenParty struct {
- relativePath string
- station *Iris // this station is where the party is happening, this station's Garden is the same for all Parties per Station & Router instance
- middleware Middleware
- root bool
- }
-)
-
-var _ IParty = &GardenParty{}
-
-// IsRoot returns true if this is the root party ("/")
-func (p *GardenParty) IsRoot() bool {
- return p.root
-}
-
-// Handle registers a route to the server's router
-// if empty method is passed then registers handler(s) for all methods, same as .Any, but returns nil as result
-func (p *GardenParty) Handle(method string, registedPath string, handlers ...Handler) IRoute {
- if method == "" { // then use like it was .Any
- for _, k := range AllMethods {
- p.Handle(k, registedPath, handlers...)
- }
- return nil
- }
- path := fixPath(p.relativePath + registedPath) // keep the last "/" as default ex: "/xyz/"
- if !p.station.config.DisablePathCorrection {
- // if we have path correction remove it with absPath
- path = fixPath(absPath(p.relativePath, registedPath)) // "/xyz"
- }
- middleware := JoinMiddleware(p.middleware, handlers)
- route := NewRoute(method, path, middleware, p.station)
- p.station.plugins.DoPreHandle(route)
- p.station.addRoute(route)
- p.station.plugins.DoPostHandle(route)
- return route
-}
-
-// HandleFunc registers and returns a route with a method string, path string and a handler
-// registedPath is the relative url path
-// handler is the iris.Handler which you can pass anything you want via iris.ToHandlerFunc(func(res,req){})... or just use func(c *iris.Context)
-func (p *GardenParty) HandleFunc(method string, registedPath string, handlersFn ...HandlerFunc) IRoute {
- return p.Handle(method, registedPath, ConvertToHandlers(handlersFn)...)
-}
-
-// Wildcard same as .Party("*.")
-// registers a route for Dynamic subdomain
-// receives three parameters
-// the first is the http method
-// the second is the request path, can be a dynamic path also like others
-// the third are the handlerfuncs
-//
-// Note that this is just a global route, no party's route.
-// example: subdomains_2
-func (p *GardenParty) Wildcard(method string, registedPath string, handlersFn ...HandlerFunc) {
- path := PrefixDynamicSubdomain + registedPath
- p.station.router.HandleFunc(method, path, handlersFn...)
-}
-
-// API converts & registers a custom struct to the router
-// receives two parameters
-// first is the request path (string)
-// second is the custom struct (interface{}) which can be anything that has a *iris.Context as field.
-// third are the common middlewares, is optional parameter
-//
-// Note that API's routes have their default-name to the full registed path,
-// no need to give a special name for it, because it's not supposed to be used inside your templates.
-//
-// Recommend to use when you retrieve data from an external database,
-// and the router-performance is not the (only) thing which slows the server's overall performance.
-//
-// This is a slow method, if you care about router-performance use the Handle/HandleFunc/Get/Post/Put/Delete/Trace/Options... instead
-//
-// Usage:
-// All the below methods are optional except the *iris.Context field,
-// example with /users :
-/*
-
-package main
-
-import (
- "github.com/kataras/iris"
-)
-
-type UserAPI struct {
- *iris.Context
-}
-
-// GET /users
-func (u UserAPI) Get() {
- u.Write("Get from /users")
- // u.JSON(iris.StatusOK,myDb.AllUsers())
-}
-
-// GET /:param1 which its value passed to the id argument
-func (u UserAPI) GetBy(id string) { // id equals to u.Param("param1")
- u.Write("Get from /users/%s", id)
- // u.JSON(iris.StatusOK, myDb.GetUserById(id))
-
-}
-
-// PUT /users
-func (u UserAPI) Put() {
- name := u.FormValue("name")
- // myDb.InsertUser(...)
- println(string(name))
- println("Put from /users")
-}
-
-// POST /users/:param1
-func (u UserAPI) PostBy(id string) {
- name := u.FormValue("name") // you can still use the whole Context's features!
- // myDb.UpdateUser(...)
- println(string(name))
- println("Post from /users/" + id)
-}
-
-// DELETE /users/:param1
-func (u UserAPI) DeleteBy(id string) {
- // myDb.DeleteUser(id)
- println("Delete from /" + id)
-}
-
-func main() {
- iris.API("/users", UserAPI{})
- iris.Listen(":80")
-}
-*/
-func (p *GardenParty) API(path string, controller HandlerAPI, middlewares ...HandlerFunc) error {
- // here we need to find the registed methods and convert them to handler funcs
- // methods are collected by method naming: Get(),GetBy(...), Post(),PostBy(...), Put() and so on
-
- typ := reflect.ValueOf(controller).Type()
- contextField, found := typ.FieldByName("Context")
- if !found {
- return ErrControllerContextNotFound.Return()
- }
-
- // check & register the Get(),Post(),Put(),Delete() and so on
- for _, methodName := range AllMethods {
-
- methodCapitalName := strings.Title(strings.ToLower(methodName))
-
- if method, found := typ.MethodByName(methodCapitalName); found {
- methodFunc := method.Func
- if !methodFunc.IsValid() || methodFunc.Type().NumIn() > 1 { // for any case
- continue
- }
-
- func(path string, typ reflect.Type, contextField reflect.StructField, methodFunc reflect.Value, method string) {
- var handlersFn []HandlerFunc
-
- handlersFn = append(handlersFn, middlewares...)
- handlersFn = append(handlersFn, func(ctx *Context) {
- newController := reflect.New(typ).Elem()
- newController.FieldByName("Context").Set(reflect.ValueOf(ctx))
- methodFunc.Call([]reflect.Value{newController})
- })
- // register route
- p.HandleFunc(method, path, handlersFn...)
- }(path, typ, contextField, methodFunc, methodName)
-
- }
-
- }
-
- // check for GetBy/PostBy(id string, something_else string) , these must be requested by the same order.
- // (we could do this in the same top loop but I don't want)
- // GET, DELETE -> with url named parameters (/users/:id/:secondArgumentIfExists)
- // POST, PUT -> with post values (form)
- // all other with URL Parameters (?something=this&else=other
- //
- // or no, I changed my mind, let all be named parameters and let users to decide what info they need,
- // using the Context to take more values (post form,url params and so on).-
-
- for _, methodName := range AllMethods {
- methodWithBy := strings.Title(strings.ToLower(methodName)) + "By"
- if method, found := typ.MethodByName(methodWithBy); found {
- methodFunc := method.Func
- if !methodFunc.IsValid() || methodFunc.Type().NumIn() < 2 { //it's By but it has not receive any arguments so its not api's
- continue
- }
- methodFuncType := methodFunc.Type()
- numInLen := methodFuncType.NumIn() // how much data we should receive from the request
- registedPath := path
-
- for i := 1; i < numInLen; i++ { // from 1 because the first is the 'object'
- if registedPath[len(registedPath)-1] == SlashByte {
- registedPath += ":param" + strconv.Itoa(i)
- } else {
- registedPath += "/:param" + strconv.Itoa(i)
- }
- }
-
- func(registedPath string, typ reflect.Type, contextField reflect.StructField, methodFunc reflect.Value, paramsLen int, method string) {
- var handlersFn []HandlerFunc
-
- handlersFn = append(handlersFn, middlewares...)
- handlersFn = append(handlersFn, func(ctx *Context) {
- newController := reflect.New(typ).Elem()
- newController.FieldByName("Context").Set(reflect.ValueOf(ctx))
- args := make([]reflect.Value, paramsLen+1, paramsLen+1)
- args[0] = newController
- for i := 0; i < paramsLen; i++ {
- args[i+1] = reflect.ValueOf(ctx.Params[i].Value)
- }
- methodFunc.Call(args)
- })
- // register route
- p.HandleFunc(method, registedPath, handlersFn...)
- }(registedPath, typ, contextField, methodFunc, numInLen-1, methodName)
-
- }
-
- }
-
- return nil
-}
-
-// Get registers a route for the Get http method
-func (p *GardenParty) Get(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return p.HandleFunc(MethodGet, path, handlersFn...).Name
-}
-
-// Post registers a route for the Post http method
-func (p *GardenParty) Post(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return p.HandleFunc(MethodPost, path, handlersFn...).Name
-}
-
-// Put registers a route for the Put http method
-func (p *GardenParty) Put(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return p.HandleFunc(MethodPut, path, handlersFn...).Name
-}
-
-// Delete registers a route for the Delete http method
-func (p *GardenParty) Delete(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return p.HandleFunc(MethodDelete, path, handlersFn...).Name
-}
-
-// Connect registers a route for the Connect http method
-func (p *GardenParty) Connect(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return p.HandleFunc(MethodConnect, path, handlersFn...).Name
-}
-
-// Head registers a route for the Head http method
-func (p *GardenParty) Head(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return p.HandleFunc(MethodHead, path, handlersFn...).Name
-}
-
-// Options registers a route for the Options http method
-func (p *GardenParty) Options(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return p.HandleFunc(MethodOptions, path, handlersFn...).Name
-}
-
-// Patch registers a route for the Patch http method
-func (p *GardenParty) Patch(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return p.HandleFunc(MethodPatch, path, handlersFn...).Name
-}
-
-// Trace registers a route for the Trace http method
-func (p *GardenParty) Trace(path string, handlersFn ...HandlerFunc) RouteNameFunc {
- return p.HandleFunc(MethodTrace, path, handlersFn...).Name
-}
-
-// Any registers a route for ALL of the http methods (Get,Post,Put,Head,Patch,Options,Connect,Delete)
-func (p *GardenParty) Any(registedPath string, handlersFn ...HandlerFunc) []IRoute {
- theRoutes := make([]IRoute, len(AllMethods), len(AllMethods))
- for idx, k := range AllMethods {
- r := p.HandleFunc(k, registedPath, handlersFn...)
- theRoutes[idx] = r
- }
-
- return theRoutes
-}
-
-// H_ is used to convert a context.IContext handler func to iris.HandlerFunc, is used only inside iris internal package to avoid import cycles
-func (p *GardenParty) H_(method string, registedPath string, fn func(context.IContext)) {
- p.HandleFunc(method, registedPath, func(ctx *Context) {
- fn(ctx)
- })
-}
-
-// Use registers a Handler middleware
-func (p *GardenParty) Use(handlers ...Handler) {
- p.middleware = append(p.middleware, handlers...)
-}
-
-// UseFunc registers a HandlerFunc middleware
-func (p *GardenParty) UseFunc(handlersFn ...HandlerFunc) {
- p.Use(ConvertToHandlers(handlersFn)...)
-}
-
-// StaticHandlerFunc returns a HandlerFunc to serve static system directory
-// Accepts 5 parameters
-//
-// first is the systemPath (string)
-// Path to the root directory to serve files from.
-//
-// second is the stripSlashes (int) level
-// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
-// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
-// * stripSlashes = 2, original path: "/foo/bar", result: ""
-//
-// third is the compress (bool)
-// Transparently compresses responses if set to true.
-//
-// The server tries minimizing CPU usage by caching compressed files.
-// It adds fasthttp.FSCompressedFileSuffix suffix to the original file name and
-// tries saving the resulting compressed file under the new file name.
-// So it is advisable to give the server write access to Root
-// and to all inner folders in order to minimze CPU usage when serving
-// compressed responses.
-//
-// fourth is the generateIndexPages (bool)
-// Index pages for directories without files matching IndexNames
-// are automatically generated if set.
-//
-// Directory index generation may be quite slow for directories
-// with many files (more than 1K), so it is discouraged enabling
-// index pages' generation for such directories.
-//
-// fifth is the indexNames ([]string)
-// List of index file names to try opening during directory access.
-//
-// For example:
-//
-// * index.html
-// * index.htm
-// * my-super-index.xml
-//
-func (p *GardenParty) StaticHandlerFunc(systemPath string, stripSlashes int, compress bool, generateIndexPages bool, indexNames []string) HandlerFunc {
- if indexNames == nil {
- indexNames = []string{}
- }
- fs := &fasthttp.FS{
- // Path to directory to serve.
- Root: systemPath,
- IndexNames: indexNames,
- // Generate index pages if client requests directory contents.
- GenerateIndexPages: generateIndexPages,
-
- // Enable transparent compression to save network traffic.
- Compress: compress,
- CacheDuration: config.StaticCacheDuration,
- CompressedFileSuffix: config.CompressedFileSuffix,
- }
-
- if stripSlashes > 0 {
- fs.PathRewrite = fasthttp.NewPathSlashesStripper(stripSlashes)
- }
-
- // Create request handler for serving static files.
- h := fs.NewRequestHandler()
- return func(ctx *Context) {
- h(ctx.RequestCtx)
- errCode := ctx.RequestCtx.Response.StatusCode()
-
- if errHandler := ctx.station.router.GetByCode(errCode); errHandler != nil {
- ctx.RequestCtx.Response.ResetBody()
- ctx.EmitError(errCode)
- }
- if ctx.pos < uint8(len(ctx.middleware))-1 {
- ctx.Next() // for any case
- }
-
- }
-}
-
-// Static registers a route which serves a system directory
-// this doesn't generates an index page which list all files
-// no compression is used also, for these features look at StaticFS func
-// accepts three parameters
-// first parameter is the request url path (string)
-// second parameter is the system directory (string)
-// third parameter is the level (int) of stripSlashes
-// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
-// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
-// * stripSlashes = 2, original path: "/foo/bar", result: ""
-func (p *GardenParty) Static(relative string, systemPath string, stripSlashes int) {
- if relative[len(relative)-1] != SlashByte { // if / then /*filepath, if /something then /something/*filepath
- relative += Slash
- }
-
- h := p.StaticHandlerFunc(systemPath, stripSlashes, false, false, nil)
-
- p.Get(relative+"*filepath", h)
- p.Head(relative+"*filepath", h)
-}
-
-// StaticFS registers a route which serves a system directory
-// this is the fastest method to serve static files
-// generates an index page which list all files
-// if you use this method it will generate compressed files also
-// think this function as small fileserver with http
-// accepts three parameters
-// first parameter is the request url path (string)
-// second parameter is the system directory (string)
-// third parameter is the level (int) of stripSlashes
-// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
-// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
-// * stripSlashes = 2, original path: "/foo/bar", result: ""
-func (p *GardenParty) StaticFS(reqPath string, systemPath string, stripSlashes int) {
- if reqPath[len(reqPath)-1] != SlashByte {
- reqPath += "/"
- }
-
- h := p.StaticHandlerFunc(systemPath, stripSlashes, true, true, nil)
- p.Get(reqPath+"*filepath", h)
- p.Head(reqPath+"*filepath", h)
-}
-
-// StaticWeb same as Static but if index.html exists and request uri is '/' then display the index.html's contents
-// accepts three parameters
-// first parameter is the request url path (string)
-// second parameter is the system directory (string)
-// third parameter is the level (int) of stripSlashes
-// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
-// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
-// * stripSlashes = 2, original path: "/foo/bar", result: ""
-// * if you don't know what to put on stripSlashes just 1
-func (p *GardenParty) StaticWeb(reqPath string, systemPath string, stripSlashes int) {
- if reqPath[len(reqPath)-1] != SlashByte { // if / then /*filepath, if /something then /something/*filepath
- reqPath += "/"
- }
-
- hasIndex := utils.Exists(systemPath + utils.PathSeparator + "index.html")
- serveHandler := p.StaticHandlerFunc(systemPath, stripSlashes, false, !hasIndex, nil) // if not index.html exists then generate index.html which shows the list of files
- indexHandler := func(ctx *Context) {
- if len(ctx.Param("filepath")) < 2 && hasIndex {
- ctx.Request.SetRequestURI("index.html")
- }
- ctx.Next()
-
- }
- p.Get(reqPath+"*filepath", indexHandler, serveHandler)
- p.Head(reqPath+"*filepath", indexHandler, serveHandler)
-}
-
-// StaticServe serves a directory as web resource
-// it's the simpliest form of the Static* functions
-// Almost same usage as StaticWeb
-// accepts only one required parameter which is the systemPath ( the same path will be used to register the GET&HEAD routes)
-// if second parameter is empty, otherwise the requestPath is the second parameter
-// it uses gzip compression (compression on each request, no file cache)
-func (p *GardenParty) StaticServe(systemPath string, requestPath ...string) {
- var reqPath string
-
- if len(reqPath) > 0 {
- reqPath = requestPath[0]
- }
-
- reqPath = strings.Replace(systemPath, utils.PathSeparator, Slash, -1) // replaces any \ to /
- reqPath = strings.Replace(reqPath, "//", Slash, -1) // for any case, replaces // to /
- reqPath = strings.Replace(reqPath, ".", "", -1) // replace any dots (./mypath -> /mypath)
-
- p.Get(reqPath+"/*file", func(ctx *Context) {
- filepath := ctx.Param("file")
-
- path := strings.Replace(filepath, "/", utils.PathSeparator, -1)
- path = absPath(systemPath, path)
-
- if !utils.DirectoryExists(path) {
- ctx.NotFound()
- return
- }
-
- ctx.ServeFile(path, true)
- })
-}
-
-/* here in order to the subdomains be able to change favicon also */
-
-// Favicon serves static favicon
-// accepts 2 parameters, second is optionally
-// favPath (string), declare the system directory path of the __.ico
-// requestPath (string), it's the route's path, by default this is the "/favicon.ico" because some browsers tries to get this by default first,
-// you can declare your own path if you have more than one favicon (desktop, mobile and so on)
-//
-// this func will add a route for you which will static serve the /yuorpath/yourfile.ico to the /yourfile.ico (nothing special that you can't handle by yourself)
-// Note that you have to call it on every favicon you have to serve automatically (dekstop, mobile and so on)
-//
-// returns an error if something goes bad
-func (p *GardenParty) Favicon(favPath string, requestPath ...string) error {
- f, err := os.Open(favPath)
- if err != nil {
- return ErrDirectoryFileNotFound.Format(favPath, err.Error())
- }
- defer f.Close()
- fi, _ := f.Stat()
- if fi.IsDir() { // if it's dir the try to get the favicon.ico
- fav := path.Join(favPath, "favicon.ico")
- f, err = os.Open(fav)
- if err != nil {
- //we try again with .png
- return p.Favicon(path.Join(favPath, "favicon.png"))
- }
- favPath = fav
- fi, _ = f.Stat()
- }
- modtime := fi.ModTime().UTC().Format(TimeFormat)
- contentType := utils.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 {
- return ErrDirectoryFileNotFound.Format(favPath, "Couldn't read the data bytes from ico: "+err.Error())
- }
-
- h := func(ctx *Context) {
- if t, err := time.Parse(TimeFormat, ctx.RequestHeader(IfModifiedSince)); err == nil && fi.ModTime().Before(t.Add(config.StaticCacheDuration)) {
- ctx.Response.Header.Del(ContentType)
- ctx.Response.Header.Del(ContentLength)
- ctx.SetStatusCode(StatusNotModified)
- return
- }
-
- ctx.Response.Header.Set(ContentType, contentType)
- ctx.Response.Header.Set(LastModified, modtime)
- ctx.SetStatusCode(StatusOK)
- ctx.Response.SetBody(cacheFav)
- }
-
- reqPath := "/favicon" + path.Ext(fi.Name()) //we could use the filename, but because standards is /favicon.ico/.png.
- if len(requestPath) > 0 {
- reqPath = requestPath[0]
- }
- p.Get(reqPath, h)
- p.Head(reqPath, h)
- return nil
-}
-
-// StaticContent serves bytes, memory cached, on the reqPath
-func (p *GardenParty) StaticContent(reqPath string, contentType string, content []byte) {
- modtime := time.Now()
- modtimeStr := modtime.UTC().Format(TimeFormat)
-
- h := func(ctx *Context) {
- if t, err := time.Parse(TimeFormat, ctx.RequestHeader(IfModifiedSince)); err == nil && modtime.Before(t.Add(config.StaticCacheDuration)) {
- ctx.Response.Header.Del(ContentType)
- ctx.Response.Header.Del(ContentLength)
- ctx.SetStatusCode(StatusNotModified)
- return
- }
-
- ctx.Response.Header.Set(ContentType, contentType)
- ctx.Response.Header.Set(LastModified, modtimeStr)
- ctx.SetStatusCode(StatusOK)
- ctx.Response.SetBody(content)
- }
- p.Get(reqPath, h)
- p.Head(reqPath, h)
-}
-
-/* */
-
-// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
-// Party can also be named as 'Join' or 'Node' or 'Group' , Party chosen because it has more fun
-func (p *GardenParty) Party(path string, handlersFn ...HandlerFunc) IParty {
- middleware := ConvertToHandlers(handlersFn)
- if path[0] != SlashByte && strings.Contains(path, ".") {
- //it's a domain so no handlers share (even the global ) or path, nothing.
- if path[0] == MatchEverythingByte { // it's a dynamic subdomain
- path = PrefixDynamicSubdomain
- }
- } else {
- // set path to parent+child
- path = absPath(p.relativePath, path)
- // append the parent's +child's handlers
- middleware = JoinMiddleware(p.middleware, middleware)
- }
-
- return &GardenParty{relativePath: path, station: p.station, middleware: middleware}
-}
-
-func absPath(rootPath string, relativePath string) (absPath string) {
-
- if relativePath == "" {
- absPath = rootPath
- } else {
- absPath = path.Join(rootPath, relativePath)
- }
-
- return
-}
-
-// fixPath fix the double slashes, (because of root,I just do that before the .Handle no need for anything else special)
-func fixPath(str string) string {
-
- strafter := strings.Replace(str, "//", Slash, -1)
-
- if strafter[0] == SlashByte && strings.Count(strafter, ".") >= 2 {
- //it's domain, remove the first slash
- strafter = strafter[1:]
- }
-
- return strafter
-}
diff --git a/plugin.go b/plugin.go
index 7b0c82ed..3d09f7bb 100644
--- a/plugin.go
+++ b/plugin.go
@@ -1,128 +1,133 @@
package iris
import (
+ "sync"
+
+ "github.com/kataras/iris/errors"
+
"github.com/kataras/iris/logger"
"github.com/kataras/iris/utils"
)
+var (
+ // errPluginAlreadyExists returns an error with message: 'Cannot activate the same plugin again, plugin '+plugin name[+plugin description]' is already exists'
+ errPluginAlreadyExists = errors.New("Cannot use the same plugin again, '%s[%s]' is already exists")
+ // errPluginActivate returns an error with message: 'While trying to activate plugin '+plugin name'. Trace: +specific error'
+ errPluginActivate = errors.New("While trying to activate plugin '%s'. Trace: %s")
+ // errPluginRemoveNoPlugins returns an error with message: 'No plugins are registed yet, you cannot remove a plugin from an empty list!'
+ errPluginRemoveNoPlugins = errors.New("No plugins are registed yet, you cannot remove a plugin from an empty list!")
+ // errPluginRemoveEmptyName returns an error with message: 'Plugin with an empty name cannot be removed'
+ errPluginRemoveEmptyName = errors.New("Plugin with an empty name cannot be removed")
+ // errPluginRemoveNotFound returns an error with message: 'Cannot remove a plugin which doesn't exists'
+ errPluginRemoveNotFound = errors.New("Cannot remove a plugin which doesn't exists")
+)
+
type (
- // IPlugin just an empty base for plugins
- // A Plugin can be added with: .Add(PreHandleFunc(func(IRoute))) and so on... or
- // .Add(myPlugin{}) which myPlugin is a struct with any of the methods below or
- // .PreHandle(PreHandleFunc), .PostHandle(func(IRoute)) and so on...
- IPlugin interface {
+ // Plugin just an empty base for plugins
+ // A Plugin can be added with: .Add(PreListenFunc(func(*Framework))) and so on... or
+ // .Add(myPlugin{},myPlugin2{}) which myPlugin is a struct with any of the methods below or
+ // .PostListen(func(*Framework)) and so on...
+ Plugin interface {
}
- // IPluginGetName implements the GetName() string method
- IPluginGetName interface {
+ // pluginGetName implements the GetName() string method
+ pluginGetName interface {
// GetName has to returns the name of the plugin, a name is unique
// name has to be not dependent from other methods of the plugin,
// because it is being called even before the Activate
GetName() string
}
- // IPluginGetDescription implements the GetDescription() string method
- IPluginGetDescription interface {
+ // pluginGetDescription implements the GetDescription() string method
+ pluginGetDescription interface {
// GetDescription has to returns the description of what the plugins is used for
GetDescription() string
}
- // IPluginActivate implements the Activate(IPluginContainer) error method
- IPluginActivate interface {
+ // pluginActivate implements the Activate(pluginContainer) error method
+ pluginActivate interface {
// Activate called BEFORE the plugin being added to the plugins list,
// if Activate returns none nil error then the plugin is not being added to the list
// it is being called only one time
//
// PluginContainer parameter used to add other plugins if that's necessary by the plugin
- Activate(IPluginContainer) error
+ Activate(PluginContainer) error
}
-
- // IPluginPreHandle implements the PreHandle(IRoute) method
- IPluginPreHandle interface {
- // PreHandle it's being called every time BEFORE a Route is registed to the Router
- //
- // parameter is the Route
- PreHandle(IRoute)
- }
- // PreHandleFunc implements the simple function listener for the PreHandle(IRoute)
- PreHandleFunc func(IRoute)
- // IPluginPostHandle implements the PostHandle(IRoute) method
- IPluginPostHandle interface {
- // PostHandle it's being called every time AFTER a Route successfully registed to the Router
- //
- // parameter is the Route
- PostHandle(IRoute)
- }
- // PostHandleFunc implements the simple function listener for the PostHandle(IRoute)
- PostHandleFunc func(IRoute)
- // IPluginPreListen implements the PreListen(*Iris) method
- IPluginPreListen interface {
+ // pluginPreListen implements the PreListen(*Framework) method
+ pluginPreListen interface {
// PreListen it's being called only one time, BEFORE the Server is started (if .Listen called)
// is used to do work at the time all other things are ready to go
// parameter is the station
- PreListen(*Iris)
+ PreListen(*Framework)
}
- // PreListenFunc implements the simple function listener for the PreListen(*Iris)
- PreListenFunc func(*Iris)
- // IPluginPostListen implements the PostListen(*Iris) method
- IPluginPostListen interface {
+ // PreListenFunc implements the simple function listener for the PreListen(*Framework)
+ PreListenFunc func(*Framework)
+ // pluginPostListen implements the PostListen(*Framework) method
+ pluginPostListen interface {
// PostListen it's being called only one time, AFTER the Server is started (if .Listen called)
// parameter is the station
- PostListen(*Iris)
+ PostListen(*Framework)
}
- // PostListenFunc implements the simple function listener for the PostListen(*Iris)
- PostListenFunc func(*Iris)
- // IPluginPreClose implements the PreClose(*Iris) method
- IPluginPreClose interface {
+ // PostListenFunc implements the simple function listener for the PostListen(*Framework)
+ PostListenFunc func(*Framework)
+ // pluginPreClose implements the PreClose(*Framework) method
+ pluginPreClose interface {
// PreClose it's being called only one time, BEFORE the Iris .Close method
// any plugin cleanup/clear memory happens here
//
// The plugin is deactivated after this state
- PreClose(*Iris)
+ PreClose(*Framework)
}
- // PreCloseFunc implements the simple function listener for the PreClose(*Iris)
- PreCloseFunc func(*Iris)
+ // PreCloseFunc implements the simple function listener for the PreClose(*Framework)
+ PreCloseFunc func(*Framework)
- // IPluginPreDownload It's for the future, not being used, I need to create
+ // pluginPreDownload It's for the future, not being used, I need to create
// and return an ActivatedPlugin type which will have it's methods, and pass it on .Activate
// but now we return the whole pluginContainer, which I can't determinate which plugin tries to
// download something, so we will leave it here for the future.
- IPluginPreDownload interface {
+ pluginPreDownload interface {
// PreDownload it's being called every time a plugin tries to download something
//
// first parameter is the plugin
// second parameter is the download url
// must return a boolean, if false then the plugin is not permmited to download this file
- PreDownload(plugin IPlugin, downloadURL string) // bool
+ PreDownload(plugin Plugin, downloadURL string) // bool
}
- // PreDownloadFunc implements the simple function listener for the PreDownload(IPlugin,string)
- PreDownloadFunc func(IPlugin, string)
+ // PreDownloadFunc implements the simple function listener for the PreDownload(plugin,string)
+ PreDownloadFunc func(Plugin, string)
- // IPluginContainer is the interface which the PluginContainer should implements
- IPluginContainer interface {
- Add(plugin IPlugin) error
- Remove(pluginName string) error
- GetName(plugin IPlugin) string
- GetDescription(plugin IPlugin) string
- GetByName(pluginName string) IPlugin
- Printf(format string, a ...interface{})
- DoPreHandle(route IRoute)
- DoPostHandle(route IRoute)
- DoPreListen(station *Iris)
- DoPostListen(station *Iris)
- DoPreClose(station *Iris)
- DoPreDownload(pluginTryToDownload IPlugin, downloadURL string)
- GetAll() []IPlugin
+ // PluginContainer is the interface which the pluginContainer should implements
+ PluginContainer interface {
+ Add(...Plugin) error
+ Remove(string) error
+ GetName(Plugin) string
+ GetDescription(Plugin) string
+ GetByName(string) Plugin
+ Printf(string, ...interface{})
+ PreListen(PreListenFunc)
+ DoPreListen(*Framework)
+ DoPreListenParallel(*Framework)
+ PostListen(PostListenFunc)
+ DoPostListen(*Framework)
+ PreClose(PreCloseFunc)
+ DoPreClose(*Framework)
+ PreDownload(PreDownloadFunc)
+ DoPreDownload(Plugin, string)
+ // custom event callbacks
+ On(string, ...func())
+ Call(string)
+ //
+ GetAll() []Plugin
// GetDownloader is the only one module that is used and fire listeners at the same time in this file
- GetDownloader() IDownloadManager
+ GetDownloader() PluginDownloadManager
}
- // IDownloadManager is the interface which the DownloadManager should implements
- IDownloadManager interface {
- DirectoryExists(dir string) bool
- DownloadZip(zipURL string, targetDir string) (string, error)
- Unzip(archive string, target string) (string, error)
- Remove(filePath string) error
+ // PluginDownloadManager is the interface which the DownloadManager should implements
+ PluginDownloadManager interface {
+ DirectoryExists(string) bool
+ DownloadZip(string, string) (string, error)
+ Unzip(string, string) (string, error)
+ Remove(string) error
// install is just the flow of: downloadZip -> unzip -> removeFile(zippedFile)
// accepts 2 parameters
//
@@ -136,37 +141,23 @@ type (
Install(remoteFileZip string, targetDirectory string) (string, error)
}
- // DownloadManager is just a struch which exports the util's downloadZip, directoryExists, unzip methods, used by the plugins via the PluginContainer
- DownloadManager struct {
+ // pluginDownloadManager is just a struch which exports the util's downloadZip, directoryExists, unzip methods, used by the plugins via the pluginContainer
+ pluginDownloadManager struct {
}
)
-// convert the functions to IPlugin
-
-// PreHandle it's being called every time BEFORE a Route is registed to the Router
-//
-// parameter is the Route
-func (fn PreHandleFunc) PreHandle(route IRoute) {
- fn(route)
-}
-
-// PostHandle it's being called every time AFTER a Route successfully registed to the Router
-//
-// parameter is the Route
-func (fn PostHandleFunc) PostHandle(route IRoute) {
- fn(route)
-}
+// convert the functions to plugin
// PreListen it's being called only one time, BEFORE the Server is started (if .Listen called)
// is used to do work at the time all other things are ready to go
// parameter is the station
-func (fn PreListenFunc) PreListen(station *Iris) {
+func (fn PreListenFunc) PreListen(station *Framework) {
fn(station)
}
// PostListen it's being called only one time, AFTER the Server is started (if .Listen called)
// parameter is the station
-func (fn PostListenFunc) PostListen(station *Iris) {
+func (fn PostListenFunc) PostListen(station *Framework) {
fn(station)
}
@@ -174,7 +165,7 @@ func (fn PostListenFunc) PostListen(station *Iris) {
// any plugin cleanup/clear memory happens here
//
// The plugin is deactivated after this state
-func (fn PreCloseFunc) PreClose(station *Iris) {
+func (fn PreCloseFunc) PreClose(station *Framework) {
fn(station)
}
@@ -183,83 +174,90 @@ func (fn PreCloseFunc) PreClose(station *Iris) {
// first parameter is the plugin
// second parameter is the download url
// must return a boolean, if false then the plugin is not permmited to download this file
-func (fn PreDownloadFunc) PreDownload(pl IPlugin, downloadURL string) {
+func (fn PreDownloadFunc) PreDownload(pl Plugin, downloadURL string) {
fn(pl, downloadURL)
}
//
-var _ IDownloadManager = &DownloadManager{}
-var _ IPluginContainer = &PluginContainer{}
+var _ PluginDownloadManager = &pluginDownloadManager{}
+var _ PluginContainer = &pluginContainer{}
// DirectoryExists returns true if a given local directory exists
-func (d *DownloadManager) DirectoryExists(dir string) bool {
+func (d *pluginDownloadManager) DirectoryExists(dir string) bool {
return utils.DirectoryExists(dir)
}
// DownloadZip downlodas a zip to the given local path location
-func (d *DownloadManager) DownloadZip(zipURL string, targetDir string) (string, error) {
+func (d *pluginDownloadManager) DownloadZip(zipURL string, targetDir string) (string, error) {
return utils.DownloadZip(zipURL, targetDir)
}
// Unzip unzips a zip to the given local path location
-func (d *DownloadManager) Unzip(archive string, target string) (string, error) {
+func (d *pluginDownloadManager) Unzip(archive string, target string) (string, error) {
return utils.Unzip(archive, target)
}
// Remove deletes/removes/rm a file
-func (d *DownloadManager) Remove(filePath string) error {
+func (d *pluginDownloadManager) Remove(filePath string) error {
return utils.RemoveFile(filePath)
}
// Install is just the flow of the: DownloadZip->Unzip->Remove the zip
-func (d *DownloadManager) Install(remoteFileZip string, targetDirectory string) (string, error) {
+func (d *pluginDownloadManager) Install(remoteFileZip string, targetDirectory string) (string, error) {
return utils.Install(remoteFileZip, targetDirectory)
}
-// PluginContainer is the base container of all Iris, registed plugins
-type PluginContainer struct {
- activatedPlugins []IPlugin
- downloader *DownloadManager
+// pluginContainer is the base container of all Iris, registed plugins
+type pluginContainer struct {
+ activatedPlugins []Plugin
+ customEvents map[string][]func()
+ downloader *pluginDownloadManager
logger *logger.Logger
}
// Add activates the plugins and if succeed then adds it to the activated plugins list
-func (p *PluginContainer) Add(plugin IPlugin) error {
- if p.activatedPlugins == nil {
- p.activatedPlugins = make([]IPlugin, 0)
- }
-
- // Check if it's a plugin first, has Activate GetName
-
- // Check if the plugin already exists
- pName := p.GetName(plugin)
- if pName != "" && p.GetByName(pName) != nil {
- return ErrPluginAlreadyExists.Format(pName, p.GetDescription(plugin))
- }
- // Activate the plugin, if no error then add it to the plugins
- if pluginObj, ok := plugin.(IPluginActivate); ok {
- err := pluginObj.Activate(p)
- if err != nil {
- return ErrPluginActivate.Format(pName, err.Error())
+func (p *pluginContainer) Add(plugins ...Plugin) error {
+ for _, plugin := range plugins {
+ if p.activatedPlugins == nil {
+ p.activatedPlugins = make([]Plugin, 0)
}
- }
- // All ok, add it to the plugins list
- p.activatedPlugins = append(p.activatedPlugins, plugin)
+ // Check if it's a plugin first, has Activate GetName
+
+ // Check if the plugin already exists
+ pName := p.GetName(plugin)
+ if pName != "" && p.GetByName(pName) != nil {
+ return errPluginAlreadyExists.Format(pName, p.GetDescription(plugin))
+ }
+ // Activate the plugin, if no error then add it to the plugins
+ if pluginObj, ok := plugin.(pluginActivate); ok {
+ err := pluginObj.Activate(p)
+ if err != nil {
+ return errPluginActivate.Format(pName, err.Error())
+ }
+ }
+
+ // All ok, add it to the plugins list
+ p.activatedPlugins = append(p.activatedPlugins, plugin)
+ }
return nil
}
+func (p *pluginContainer) Reset() {
+
+}
+
// Remove removes a plugin by it's name, if pluginName is empty "" or no plugin found with this name, then nothing is removed and a specific error is returned.
// This doesn't calls the PreClose method
-func (p *PluginContainer) Remove(pluginName string) error {
+func (p *pluginContainer) Remove(pluginName string) error {
if p.activatedPlugins == nil {
- return ErrPluginRemoveNoPlugins.Return()
+ return errPluginRemoveNoPlugins.Return()
}
if pluginName == "" {
//return error: cannot delete an unamed plugin
- return ErrPluginRemoveEmptyName.Return()
+ return errPluginRemoveEmptyName.Return()
}
indexToRemove := -1
@@ -269,7 +267,7 @@ func (p *PluginContainer) Remove(pluginName string) error {
}
}
if indexToRemove == -1 { //if index stills -1 then no plugin was found with this name, just return an error. it is not a critical error.
- return ErrPluginRemoveNotFound.Return()
+ return errPluginRemoveNotFound.Return()
}
p.activatedPlugins = append(p.activatedPlugins[:indexToRemove], p.activatedPlugins[indexToRemove+1:]...)
@@ -278,29 +276,29 @@ func (p *PluginContainer) Remove(pluginName string) error {
}
// GetName returns the name of a plugin, if no GetName() implemented it returns an empty string ""
-func (p *PluginContainer) GetName(plugin IPlugin) string {
- if pluginObj, ok := plugin.(IPluginGetName); ok {
+func (p *pluginContainer) GetName(plugin Plugin) string {
+ if pluginObj, ok := plugin.(pluginGetName); ok {
return pluginObj.GetName()
}
return ""
}
// GetDescription returns the name of a plugin, if no GetDescription() implemented it returns an empty string ""
-func (p *PluginContainer) GetDescription(plugin IPlugin) string {
- if pluginObj, ok := plugin.(IPluginGetDescription); ok {
+func (p *pluginContainer) GetDescription(plugin Plugin) string {
+ if pluginObj, ok := plugin.(pluginGetDescription); ok {
return pluginObj.GetDescription()
}
return ""
}
// GetByName returns a plugin instance by it's name
-func (p *PluginContainer) GetByName(pluginName string) IPlugin {
+func (p *pluginContainer) GetByName(pluginName string) Plugin {
if p.activatedPlugins == nil {
return nil
}
for i := range p.activatedPlugins {
- if pluginObj, ok := p.activatedPlugins[i].(IPluginGetName); ok {
+ if pluginObj, ok := p.activatedPlugins[i].(pluginGetName); ok {
if pluginObj.GetName() == pluginName {
return pluginObj
}
@@ -311,114 +309,130 @@ func (p *PluginContainer) GetByName(pluginName string) IPlugin {
}
// GetAll returns all activated plugins
-func (p *PluginContainer) GetAll() []IPlugin {
+func (p *pluginContainer) GetAll() []Plugin {
return p.activatedPlugins
}
// GetDownloader returns the download manager
-func (p *PluginContainer) GetDownloader() IDownloadManager {
+func (p *pluginContainer) GetDownloader() PluginDownloadManager {
// create it if and only if it used somewhere
if p.downloader == nil {
- p.downloader = &DownloadManager{}
+ p.downloader = &pluginDownloadManager{}
}
return p.downloader
}
// Printf sends plain text to any registed logger (future), some plugins maybe want use this method
// maybe at the future I change it, instead of sync even-driven to async channels...
-func (p *PluginContainer) Printf(format string, a ...interface{}) {
+func (p *pluginContainer) Printf(format string, a ...interface{}) {
if p.logger != nil {
p.logger.Printf(format, a...) //for now just this.
}
}
-// PreHandle adds a PreHandle plugin-function to the plugin flow container
-func (p *PluginContainer) PreHandle(fn PreHandleFunc) {
- p.Add(fn)
-}
-
-// DoPreHandle raise all plugins which has the PreHandle method
-func (p *PluginContainer) DoPreHandle(route IRoute) {
- for i := range p.activatedPlugins {
- // check if this method exists on our plugin obj, these are optionaly and call it
- if pluginObj, ok := p.activatedPlugins[i].(IPluginPreHandle); ok {
- pluginObj.PreHandle(route)
- }
- }
-}
-
-// PostHandle adds a PostHandle plugin-function to the plugin flow container
-func (p *PluginContainer) PostHandle(fn PostHandleFunc) {
- p.Add(fn)
-}
-
-// DoPostHandle raise all plugins which has the DoPostHandle method
-func (p *PluginContainer) DoPostHandle(route IRoute) {
- for i := range p.activatedPlugins {
- // check if this method exists on our plugin obj, these are optionaly and call it
- if pluginObj, ok := p.activatedPlugins[i].(IPluginPostHandle); ok {
- pluginObj.PostHandle(route)
- }
- }
-}
-
// PreListen adds a PreListen plugin-function to the plugin flow container
-func (p *PluginContainer) PreListen(fn PreListenFunc) {
+func (p *pluginContainer) PreListen(fn PreListenFunc) {
p.Add(fn)
}
// DoPreListen raise all plugins which has the DoPreListen method
-func (p *PluginContainer) DoPreListen(station *Iris) {
+func (p *pluginContainer) DoPreListen(station *Framework) {
for i := range p.activatedPlugins {
// check if this method exists on our plugin obj, these are optionaly and call it
- if pluginObj, ok := p.activatedPlugins[i].(IPluginPreListen); ok {
+ if pluginObj, ok := p.activatedPlugins[i].(pluginPreListen); ok {
pluginObj.PreListen(station)
}
}
}
+// DoPreListenParallel raise all PreListen plugins 'at the same time'
+func (p *pluginContainer) DoPreListenParallel(station *Framework) {
+ var wg sync.WaitGroup
+
+ for _, plugin := range p.activatedPlugins {
+ wg.Add(1)
+ // check if this method exists on our plugin obj, these are optionaly and call it
+ go func(plugin Plugin) {
+ if pluginObj, ok := plugin.(pluginPreListen); ok {
+ pluginObj.PreListen(station)
+ }
+
+ wg.Done()
+
+ }(plugin)
+ }
+
+ wg.Wait()
+
+}
+
// PostListen adds a PostListen plugin-function to the plugin flow container
-func (p *PluginContainer) PostListen(fn PostListenFunc) {
+func (p *pluginContainer) PostListen(fn PostListenFunc) {
p.Add(fn)
}
// DoPostListen raise all plugins which has the DoPostListen method
-func (p *PluginContainer) DoPostListen(station *Iris) {
+func (p *pluginContainer) DoPostListen(station *Framework) {
for i := range p.activatedPlugins {
// check if this method exists on our plugin obj, these are optionaly and call it
- if pluginObj, ok := p.activatedPlugins[i].(IPluginPostListen); ok {
+ if pluginObj, ok := p.activatedPlugins[i].(pluginPostListen); ok {
pluginObj.PostListen(station)
}
}
}
// PreClose adds a PreClose plugin-function to the plugin flow container
-func (p *PluginContainer) PreClose(fn PreCloseFunc) {
+func (p *pluginContainer) PreClose(fn PreCloseFunc) {
p.Add(fn)
}
// DoPreClose raise all plugins which has the DoPreClose method
-func (p *PluginContainer) DoPreClose(station *Iris) {
+func (p *pluginContainer) DoPreClose(station *Framework) {
for i := range p.activatedPlugins {
// check if this method exists on our plugin obj, these are optionaly and call it
- if pluginObj, ok := p.activatedPlugins[i].(IPluginPreClose); ok {
+ if pluginObj, ok := p.activatedPlugins[i].(pluginPreClose); ok {
pluginObj.PreClose(station)
}
}
}
// PreDownload adds a PreDownload plugin-function to the plugin flow container
-func (p *PluginContainer) PreDownload(fn PreDownloadFunc) {
+func (p *pluginContainer) PreDownload(fn PreDownloadFunc) {
p.Add(fn)
}
// DoPreDownload raise all plugins which has the DoPreDownload method
-func (p *PluginContainer) DoPreDownload(pluginTryToDownload IPlugin, downloadURL string) {
+func (p *pluginContainer) DoPreDownload(pluginTryToDownload Plugin, downloadURL string) {
for i := range p.activatedPlugins {
// check if this method exists on our plugin obj, these are optionaly and call it
- if pluginObj, ok := p.activatedPlugins[i].(IPluginPreDownload); ok {
+ if pluginObj, ok := p.activatedPlugins[i].(pluginPreDownload); ok {
pluginObj.PreDownload(pluginTryToDownload, downloadURL)
}
}
}
+
+// On registers a custom event
+// these are not registed as plugins, they are hidden events
+func (p *pluginContainer) On(name string, fns ...func()) {
+ if p.customEvents == nil {
+ p.customEvents = make(map[string][]func(), 0)
+ }
+ if p.customEvents[name] == nil {
+ p.customEvents[name] = make([]func(), 0)
+ }
+ p.customEvents[name] = append(p.customEvents[name], fns...)
+}
+
+// Call fires the custom event
+func (p *pluginContainer) Call(name string) {
+ if p.customEvents == nil {
+ return
+ }
+ if fns := p.customEvents[name]; fns != nil {
+ for _, fn := range fns {
+ fn()
+ }
+
+ }
+}
diff --git a/plugin/editor/README.md b/plugin/editor/README.md
index c672afb1..04f9f6a8 100644
--- a/plugin/editor/README.md
+++ b/plugin/editor/README.md
@@ -24,7 +24,7 @@ import (
func main(){
e := editor.New("username","password").Port(4444).Dir("/path/to/the/client/side/directory")
- iris.Plugins().Add(e)
+ iris.Plugins.Add(e)
iris.Get("/", func (ctx *iris.Context){})
diff --git a/plugin/editor/editor.go b/plugin/editor/editor.go
index e8c925ed..6666d9e3 100644
--- a/plugin/editor/editor.go
+++ b/plugin/editor/editor.go
@@ -11,10 +11,10 @@ import (
"strconv"
"strings"
+ "github.com/iris-contrib/npm"
"github.com/kataras/iris"
"github.com/kataras/iris/config"
"github.com/kataras/iris/logger"
- "github.com/kataras/iris/npm"
"github.com/kataras/iris/utils"
)
@@ -85,13 +85,13 @@ func (e *Plugin) GetDescription() string {
}
// PreListen runs before the server's listens, saves the keyfile,certfile and the host from the Iris station to listen for
-func (e *Plugin) PreListen(s *iris.Iris) {
- e.logger = s.Logger()
- e.keyfile = s.Server().Config.KeyFile
- e.certfile = s.Server().Config.CertFile
+func (e *Plugin) PreListen(s *iris.Framework) {
+ e.logger = s.Logger
+ e.keyfile = s.Config.Server.KeyFile
+ e.certfile = s.Config.Server.CertFile
if e.config.Host == "" {
- h := s.Server().Config.ListeningAddr
+ h := s.Config.Server.ListeningAddr
if idx := strings.Index(h, ":"); idx >= 0 {
h = h[0:idx]
@@ -107,7 +107,7 @@ func (e *Plugin) PreListen(s *iris.Iris) {
}
// PreClose kills the editor's server when Iris is closed
-func (e *Plugin) PreClose(s *iris.Iris) {
+func (e *Plugin) PreClose(s *iris.Framework) {
if e.process != nil {
err := e.process.Kill()
if err != nil {
diff --git a/plugin/iriscontrol/README.md b/plugin/iriscontrol/README.md
index b1ed1196..0be191b5 100644
--- a/plugin/iriscontrol/README.md
+++ b/plugin/iriscontrol/README.md
@@ -28,7 +28,7 @@ import (
func main() {
- iris.Plugins().Add(iriscontrol.Web(9090, map[string]string{
+ iris.Plugins.Add(iriscontrol.Web(9090, map[string]string{
"irisusername1": "irispassword1",
"irisusername2": "irispassowrd2",
}))
@@ -39,7 +39,6 @@ func main() {
iris.Post("/something", func(ctx *iris.Context) {
})
- fmt.Printf("Iris is listening on :%d", 8080)
iris.Listen(":8080")
}
diff --git a/plugin/iriscontrol/control_panel.go b/plugin/iriscontrol/control_panel.go
index bb33c005..a85c6793 100644
--- a/plugin/iriscontrol/control_panel.go
+++ b/plugin/iriscontrol/control_panel.go
@@ -7,7 +7,6 @@ import (
"github.com/kataras/iris"
"github.com/kataras/iris/config"
- "github.com/kataras/iris/plugin/routesinfo"
)
var pathSeperator = string(os.PathSeparator)
@@ -27,13 +26,13 @@ func (i *irisControlPlugin) startControlPanel() {
}
i.server = iris.New()
- i.server.Config().DisableBanner = true
- i.server.Config().Render.Template.Directory = installationPath + "templates"
+ i.server.Config.DisableBanner = true
+ i.server.Config.Render.Template.Directory = installationPath + "templates"
//i.server.SetRenderConfig(i.server.Config.Render)
i.setPluginsInfo()
i.setPanelRoutes()
- go i.server.Listen(strconv.Itoa(i.options.Port))
+ go i.server.Listen(":" + strconv.Itoa(i.options.Port))
i.pluginContainer.Printf("[%s] %s is running at port %d", time.Now().UTC().String(), Name, i.options.Port)
@@ -43,7 +42,7 @@ func (i *irisControlPlugin) startControlPanel() {
// contains a boolean if server is running, the routes and the plugins
type DashboardPage struct {
ServerIsRunning bool
- Routes []routesinfo.RouteInfo
+ Routes []iris.Route
Plugins []PluginInfo
LastOperationDateStr string
}
@@ -52,7 +51,18 @@ func (i *irisControlPlugin) setPluginsInfo() {
plugins := i.pluginContainer.GetAll()
i.plugins = make([]PluginInfo, 0, len(plugins))
for _, plugin := range plugins {
- i.plugins = append(i.plugins, PluginInfo{Name: i.pluginContainer.GetName(plugin), Description: i.pluginContainer.GetDescription(plugin)})
+ name := i.pluginContainer.GetName(plugin)
+ desc := i.pluginContainer.GetDescription(plugin)
+ if name == "" {
+ // means an iris internaly plugin or a nameless plugin
+ name = "Internal Iris Plugin"
+ }
+ if desc == "" {
+ // means an iris internaly plugin or a descriptionless plugin
+ desc = "Propably an internal Iris Plugin - no description provided"
+ }
+
+ i.plugins = append(i.plugins, PluginInfo{Name: name, Description: desc})
}
}
@@ -75,8 +85,8 @@ func (i *irisControlPlugin) setPanelRoutes() {
i.server.Use(i.authFunc)
i.server.Get("/", func(ctx *iris.Context) {
ctx.Render("index.html", DashboardPage{
- ServerIsRunning: i.station.Server().IsListening(),
- Routes: i.routes.All(),
+ ServerIsRunning: i.station.HTTPServer.IsListening(),
+ Routes: i.routes,
Plugins: i.plugins,
LastOperationDateStr: i.lastOperationDate.Format(config.TimeFormat),
})
diff --git a/plugin/iriscontrol/index.go b/plugin/iriscontrol/index.go
index 4c0edb2e..4355dea2 100644
--- a/plugin/iriscontrol/index.go
+++ b/plugin/iriscontrol/index.go
@@ -1,11 +1,7 @@
package iriscontrol
-// NOT READY YET
-
-// PluginInfo holds the Name and the description of the registed plugins
+// PluginInfo the name and the description of a plugin
type PluginInfo struct {
Name string
Description string
}
-
-//func getPluginlist...
diff --git a/plugin/iriscontrol/iriscontrol.go b/plugin/iriscontrol/iriscontrol.go
index 1cabb579..1d9e91d1 100644
--- a/plugin/iriscontrol/iriscontrol.go
+++ b/plugin/iriscontrol/iriscontrol.go
@@ -6,8 +6,6 @@ import (
"github.com/kataras/iris"
"github.com/kataras/iris/config"
"github.com/kataras/iris/middleware/basicauth"
- "github.com/kataras/iris/plugin/routesinfo"
- "github.com/kataras/iris/server"
)
// Name the name(string) of this plugin which is Iris Control
@@ -16,17 +14,17 @@ const Name = "Iris Control"
type irisControlPlugin struct {
options config.IrisControl
// the pluginContainer is the container which keeps this plugin from the main user's iris instance
- pluginContainer iris.IPluginContainer
+ pluginContainer iris.PluginContainer
// the station object of the main user's iris instance
- station *iris.Iris
+ station *iris.Framework
//a copy of the server which the main user's iris is listening for
- stationServer *server.Server
+ stationServer *iris.Server
// the server is this plugin's server object, it is managed by this plugin only
- server *iris.Iris
+ server *iris.Framework
//
//infos
- routes *routesinfo.Plugin
+ routes []iris.Route
plugins []PluginInfo
// last time the server was on
lastOperationDate time.Time
@@ -37,7 +35,7 @@ type irisControlPlugin struct {
// New returns the plugin which is ready-to-use inside iris.Plugin method
// receives config.IrisControl
-func New(cfg ...config.IrisControl) iris.IPlugin {
+func New(cfg ...config.IrisControl) iris.Plugin {
c := config.DefaultIrisControl()
if len(cfg) > 0 {
c = cfg[0]
@@ -48,21 +46,20 @@ func New(cfg ...config.IrisControl) iris.IPlugin {
auth := basicauth.Default(c.Users)
- return &irisControlPlugin{options: c, authFunc: auth, routes: routesinfo.RoutesInfo()}
+ return &irisControlPlugin{options: c, authFunc: auth, routes: make([]iris.Route, 0)}
}
// Web set the options for the plugin and return the plugin which is ready-to-use inside iris.Plugin method
// first parameter is port
// second parameter is map of users (username:password)
-func Web(port int, users map[string]string) iris.IPlugin {
- return New(config.IrisControl{port, users})
+func Web(port int, users map[string]string) iris.Plugin {
+ return New(config.IrisControl{Port: port, Users: users})
}
// implement the base IPlugin
-func (i *irisControlPlugin) Activate(container iris.IPluginContainer) error {
+func (i *irisControlPlugin) Activate(container iris.PluginContainer) error {
i.pluginContainer = container
- container.Add(i.routes) // add the routesinfo plugin to the main server
return nil
}
@@ -78,25 +75,21 @@ func (i irisControlPlugin) GetDescription() string {
// implement the rest of the plugin
-// PostHandle
-func (i *irisControlPlugin) PostHandle(route iris.IRoute) {
-
-}
-
// PostListen sets the station object after the main server starts
// starts the actual work of the plugin
-func (i *irisControlPlugin) PostListen(s *iris.Iris) {
+func (i *irisControlPlugin) PostListen(s *iris.Framework) {
//if the first time, because other times start/stop of the server so listen and no listen will be only from the control panel
if i.station == nil {
i.station = s
- i.stationServer = i.station.Server()
+ i.stationServer = i.station.HTTPServer
i.lastOperationDate = time.Now()
+ i.routes = s.Lookups()
i.startControlPanel()
}
}
-func (i *irisControlPlugin) PreClose(s *iris.Iris) {
+func (i *irisControlPlugin) PreClose(s *iris.Framework) {
// Do nothing. This is a wrapper of the main server if we destroy when users stop the main server then we cannot continue the control panel i.Destroy()
}
diff --git a/plugin/iriscontrol/main_controls.go b/plugin/iriscontrol/main_controls.go
index 4156ba2b..9b17124b 100644
--- a/plugin/iriscontrol/main_controls.go
+++ b/plugin/iriscontrol/main_controls.go
@@ -2,19 +2,19 @@ package iriscontrol
// for the main server
func (i *irisControlPlugin) StartServer() {
- if i.station.Server().IsListening() == false {
- if i.station.Server().IsSecure() {
+ if i.station.HTTPServer.IsListening() == false {
+ if i.station.HTTPServer.IsSecure() {
//listen with ListenTLS
- i.station.ListenTLS(i.station.Server().Config.ListeningAddr, i.station.Server().Config.CertFile, i.station.Server().Config.KeyFile)
+ i.station.ListenTLS(i.station.Config.Server.ListeningAddr, i.station.Config.Server.CertFile, i.station.Config.Server.KeyFile)
} else {
//listen normal
- i.station.Listen(i.station.Server().Config.ListeningAddr)
+ i.station.Listen(i.station.Config.Server.ListeningAddr)
}
}
}
func (i *irisControlPlugin) StopServer() {
- if i.station.Server().IsListening() {
+ if i.station.HTTPServer.IsListening() {
i.station.Close()
}
}
diff --git a/plugin/routesinfo/README.md b/plugin/routesinfo/README.md
deleted file mode 100644
index cd210554..00000000
--- a/plugin/routesinfo/README.md
+++ /dev/null
@@ -1,61 +0,0 @@
-## RoutesInfo plugin
-
-This plugin collects & stores all registered routes and gives information about them.
-
-#### The RouteInfo
-
-```go
-
-type RouteInfo struct {
- Method string
- Domain string
- Path string
- RegistedAt time.Time
-}
-
-```
-## How to use
-
-```go
-
-package main
-
-import (
- "github.com/kataras/iris"
- "github.com/kataras/iris/plugin/routesinfo"
-)
-
-func main() {
-
- info := routesinfo.New()
- iris.Plugins().Add(info)
-
- iris.Get("/yourpath", func(c *iris.Context) {
- c.Write("yourpath")
- })
-
- iris.Post("/otherpostpath", func(c *iris.Context) {
- c.Write("other post path")
- })
-
- all := info.All()
- // allget := info.ByMethod("GET") -> slice
- // alllocalhost := info.ByDomain("localhost") -> slice
- // bypath:= info.ByPath("/yourpath") -> slice
- // bydomainandmethod:= info.ByDomainAndMethod("localhost","GET") -> slice
- // bymethodandpath:= info.ByMethodAndPath("GET","/yourpath") -> single (it could be slice for all domains too but it's not)
-
- println("The first registed route was: ", all[0].Path, "registed at: ", all[0].RegistedAt.String())
- println("All routes info:")
- for i:= range all {
- println(all[i].String())
- //outputs->
- // Domain: localhost Method: GET Path: /yourpath RegistedAt: 2016/03/27 15:27:05:029 ...
- // Domain: localhost Method: POST Path: /otherpostpath RegistedAt: 2016/03/27 15:27:05:030 ...
- }
- iris.Listen(":8080")
-
-}
-
-
-```
diff --git a/plugin/routesinfo/routesinfo.go b/plugin/routesinfo/routesinfo.go
deleted file mode 100644
index 97f0cda3..00000000
--- a/plugin/routesinfo/routesinfo.go
+++ /dev/null
@@ -1,151 +0,0 @@
-package routesinfo
-
-import (
- "fmt"
- "strings"
- "time"
-
- "github.com/kataras/iris"
-)
-
-//Name the name of the plugin, is "RoutesInfo"
-const Name = "RoutesInfo"
-
-// RouteInfo holds the method, domain, path and registered time of a route
-type RouteInfo struct {
- Method string
- Domain string
- Path string
- RegistedAt time.Time
-}
-
-// String returns the string presentation of the Route(Info)
-func (ri RouteInfo) String() string {
- if ri.Domain == "" {
- ri.Domain = "localhost" // only for printing, this doesn't save it, no pointer.
- }
- return fmt.Sprintf("Domain: %s Method: %s Path: %s RegistedAt: %s", ri.Domain, ri.Method, ri.Path, ri.RegistedAt.String())
-}
-
-// Plugin the routes info plugin, holds the routes as RouteInfo objects
-type Plugin struct {
- routes []RouteInfo
-}
-
-// implement the base IPlugin
-
-// GetName ...
-func (r Plugin) GetName() string {
- return Name
-}
-
-// GetDescription RoutesInfo gives information about the registed routes
-func (r Plugin) GetDescription() string {
- return Name + " gives information about the registed routes.\n"
-}
-
-//
-
-// implement the rest of the plugin
-
-// PostHandle collect the registed routes information
-func (r *Plugin) PostHandle(route iris.IRoute) {
- if r.routes == nil {
- r.routes = make([]RouteInfo, 0)
- }
- r.routes = append(r.routes, RouteInfo{route.GetMethod(), route.GetDomain(), route.GetPath(), time.Now()})
-}
-
-// All returns all routeinfos
-// returns a slice
-func (r Plugin) All() []RouteInfo {
- return r.routes
-}
-
-// ByDomain returns all routeinfos which registed to a specific domain
-// returns a slice, if nothing founds this slice has 0 len&cap
-func (r Plugin) ByDomain(domain string) []RouteInfo {
- var routesByDomain []RouteInfo
- rlen := len(r.routes)
- if domain == "localhost" || domain == "127.0.0.1" || domain == ":" {
- domain = ""
- }
- for i := 0; i < rlen; i++ {
- if r.routes[i].Domain == domain {
- routesByDomain = append(routesByDomain, r.routes[i])
- }
- }
- return routesByDomain
-}
-
-// ByMethod returns all routeinfos by a http method
-// returns a slice, if nothing founds this slice has 0 len&cap
-func (r Plugin) ByMethod(method string) []RouteInfo {
- var routesByMethod []RouteInfo
- rlen := len(r.routes)
- method = strings.ToUpper(method)
- for i := 0; i < rlen; i++ {
- if r.routes[i].Method == method {
- routesByMethod = append(routesByMethod, r.routes[i])
- }
- }
- return routesByMethod
-}
-
-// ByPath returns all routeinfos by a path
-// maybe one path is the same on GET and POST ( for example /login GET, /login POST)
-// because of that it returns a slice and not only one RouteInfo
-// returns a slice, if nothing founds this slice has 0 len&cap
-func (r Plugin) ByPath(path string) []RouteInfo {
- var routesByPath []RouteInfo
- rlen := len(r.routes)
- for i := 0; i < rlen; i++ {
- if r.routes[i].Path == path {
- routesByPath = append(routesByPath, r.routes[i])
- }
- }
- return routesByPath
-}
-
-// ByDomainAndMethod returns all routeinfos registed to a specific domain and has specific http method
-// returns a slice, if nothing founds this slice has 0 len&cap
-func (r Plugin) ByDomainAndMethod(domain string, method string) []RouteInfo {
- var routesByDomainAndMethod []RouteInfo
- rlen := len(r.routes)
- method = strings.ToUpper(method)
- if domain == "localhost" || domain == "127.0.0.1" || domain == ":" {
- domain = ""
- }
-
- for i := 0; i < rlen; i++ {
- if r.routes[i].Method == method && r.routes[i].Domain == domain {
- routesByDomainAndMethod = append(routesByDomainAndMethod, r.routes[i])
- }
- }
- return routesByDomainAndMethod
-}
-
-// ByMethodAndPath returns a single *RouteInfo which has specific http method and path
-// returns only the first match
-// if nothing founds returns nil
-func (r Plugin) ByMethodAndPath(method string, path string) *RouteInfo {
-
- rlen := len(r.routes)
- for i := 0; i < rlen; i++ {
- if r.routes[i].Method == method && r.routes[i].Path == path {
- return &r.routes[i]
- }
- }
- return nil
-}
-
-//
-// RoutesInfo returns the Plugin, same as New()
-func RoutesInfo() *Plugin {
- return &Plugin{}
-}
-
-// New returns the Plugin, same as RoutesInfo()
-func New() *Plugin {
- return &Plugin{}
-}
diff --git a/plugin/typescript/README.md b/plugin/typescript/README.md
index b9463119..0c3b70a0 100644
--- a/plugin/typescript/README.md
+++ b/plugin/typescript/README.md
@@ -55,7 +55,7 @@ func main(){
ts = typescript.DefaultOptions()
//
- iris.Plugins().Add(typescript.New(ts)) //or with the default options just: typescript.New()
+ iris.Plugins.Add(typescript.New(ts)) //or with the default options just: typescript.New()
iris.Get("/", func (ctx *iris.Context){})
diff --git a/plugin/typescript/typescript.go b/plugin/typescript/typescript.go
index 08381e12..6b9ebe2e 100644
--- a/plugin/typescript/typescript.go
+++ b/plugin/typescript/typescript.go
@@ -6,10 +6,10 @@ import (
"path/filepath"
"strings"
+ "github.com/iris-contrib/npm"
"github.com/kataras/iris"
"github.com/kataras/iris/config"
"github.com/kataras/iris/logger"
- "github.com/kataras/iris/npm"
"github.com/kataras/iris/plugin/editor"
"github.com/kataras/iris/utils"
)
@@ -46,7 +46,7 @@ type (
Plugin struct {
options Options
// taken from Activate
- pluginContainer iris.IPluginContainer
+ pluginContainer iris.PluginContainer
// taken at the PreListen
logger *logger.Logger
}
@@ -106,7 +106,7 @@ func New(_opt ...Options) *Plugin {
// implement the IPlugin & IPluginPreListen
// Activate ...
-func (t *Plugin) Activate(container iris.IPluginContainer) error {
+func (t *Plugin) Activate(container iris.PluginContainer) error {
t.pluginContainer = container
return nil
}
@@ -122,8 +122,8 @@ func (t *Plugin) GetDescription() string {
}
// PreListen ...
-func (t *Plugin) PreListen(s *iris.Iris) {
- t.logger = s.Logger()
+func (t *Plugin) PreListen(s *iris.Framework) {
+ t.logger = s.Logger
t.start()
}
diff --git a/render/template/engine/pongo/pongo.go b/render/template/engine/pongo/pongo.go
index e5f946e6..113d14ed 100644
--- a/render/template/engine/pongo/pongo.go
+++ b/render/template/engine/pongo/pongo.go
@@ -152,15 +152,11 @@ func getPongoContext(templateData interface{}) pongo2.Context {
return nil
}
- if v, isMap := templateData.(map[string]interface{}); isMap {
- return v
- }
-
if contextData, isPongoContext := templateData.(pongo2.Context); isPongoContext {
return contextData
}
- return nil
+ return templateData.(map[string]interface{})
}
func (p *Engine) fromCache(relativeName string) *pongo2.Template {
diff --git a/render/template/template.go b/render/template/template.go
index f0f8d6c2..5a5f9016 100644
--- a/render/template/template.go
+++ b/render/template/template.go
@@ -132,11 +132,23 @@ func RegisterSharedFunc(name string, fn interface{}) {
sharedFuncs[name] = fn
}
+// RegisterSharedFuncs registers functionalities that should be inherited from all supported template engines
+func RegisterSharedFuncs(theFuncs map[string]interface{}) {
+ if sharedFuncs == nil || len(sharedFuncs) == 0 {
+ sharedFuncs = theFuncs
+ return
+ }
+ for k, v := range theFuncs {
+ sharedFuncs[k] = v
+ }
+
+}
+
// Render renders a template using the context's writer
func (t *Template) Render(ctx context.IContext, name string, binding interface{}, layout ...string) (err error) {
if t == nil { // No engine was given but .Render was called
- ctx.WriteHTML(403, " Iris
Templates are disabled via config.NoEngine, check your iris' configuration please.")
+ ctx.HTML(403, " Iris
Templates are disabled via config.NoEngine, check your iris' configuration please.")
return fmt.Errorf("[IRIS TEMPLATES] Templates are disabled via config.NoEngine, check your iris' configuration please.\n")
}
diff --git a/route.go b/route.go
deleted file mode 100644
index fd063e3f..00000000
--- a/route.go
+++ /dev/null
@@ -1,280 +0,0 @@
-package iris
-
-import (
- "fmt"
- "strconv"
- "strings"
-)
-
-type (
- // IRoute is the interface which the Route should implements
- // it useful to have it as an interface because this interface is passed to the plugins
- IRoute interface {
- GetMethod() string
- GetDomain() string
- GetPath() string
- GetName() string
- // Name sets the name of the route
- Name(string) IRoute
- GetMiddleware() Middleware
- HasCors() bool
- ParsePath(...interface{}) string
- ParseURI(...interface{}) string
- }
-
- // RouteNameFunc is returned to from route handle
- RouteNameFunc func(string) IRoute
-
- // Route contains basic and temporary info about the route in order to be stored to the tree
- Route struct {
- method string
- domain string
- fullpath string
- // the name of the route, the default name is just the registed path.
- name string
- middleware Middleware
- // station
- station *Iris
- // this is used to convert /mypath/:aparam/:something to -> /mypath/%v/%v and /mypath/* -> mypath/%v
- // we use %v to escape from the conversions between strings,booleans and integers.
- // used inside custom html template func 'url'
- formattedPath string
- // formattedParts is just the formattedPath count, used to see if we have one path parameter then the url's function arguments will be passed as one string to the %v
- formattedParts int
- }
-)
-
-var _ IRoute = &Route{}
-
-// NewRoute creates, from a path string, and a slice of HandlerFunc
-func NewRoute(method string, registedPath string, middleware Middleware, station *Iris) *Route {
- domain := ""
- //dirdy but I'm not touching this again:P
- if registedPath[0] != SlashByte && strings.Contains(registedPath, ".") && (strings.IndexByte(registedPath, SlashByte) == -1 || strings.IndexByte(registedPath, SlashByte) > strings.IndexByte(registedPath, '.')) {
- //means that is a path with domain
- //we have to extract the domain
-
- //find the first '/'
- firstSlashIndex := strings.IndexByte(registedPath, SlashByte)
-
- //firt of all remove the first '/' if that exists and we have domain
- if firstSlashIndex == 0 {
- //e.g /admin.ideopod.com/hey
- //then just remove the first slash and re-execute the NewRoute and return it
- registedPath = registedPath[1:]
- return NewRoute(method, registedPath, middleware, station)
- }
- //if it's just the domain, then set it(registedPath) as the domain
- //and after set the registedPath to a slash '/' for the path part
- if firstSlashIndex == -1 {
- domain = registedPath
- registedPath = Slash
- } else {
- //we have a domain + path
- domain = registedPath[0:firstSlashIndex]
- registedPath = registedPath[len(domain):]
- }
-
- }
- r := &Route{method: method, domain: domain, fullpath: registedPath, middleware: middleware, name: registedPath, formattedPath: registedPath, station: station}
- r.formatPath()
- return r
-}
-
-func (r *Route) isWildcard() bool {
- return r.domain != r.station.server.Hostname() && r.domain == PrefixDynamicSubdomain
-}
-
-func (r *Route) formatPath() {
- // we don't care about performance here, no runtime func.
-
- n1Len := strings.Count(r.fullpath, ":")
- isMatchEverything := r.fullpath[len(r.fullpath)-1] == MatchEverythingByte
- if n1Len == 0 && !isMatchEverything {
- // its a static
- return
- }
- if n1Len == 0 && isMatchEverything {
- //if we have something like: /mypath/anything/* -> /mypatch/anything/%v
- r.formattedPath = r.fullpath[0:len(r.fullpath)-2] + "%v"
- r.formattedParts++
- return
- }
-
- tempPath := r.fullpath
-
- splittedN1 := strings.Split(r.fullpath, "/")
-
- for _, v := range splittedN1 {
- if len(v) > 0 {
- if v[0] == ':' || v[0] == MatchEverythingByte {
- r.formattedParts++
- tempPath = strings.Replace(tempPath, v, "%v", -1) // n1Len, but let it we don't care about performance here.
- }
- }
-
- }
- r.formattedPath = tempPath
-}
-
-// GetMethod returns the http method
-func (r Route) GetMethod() string {
- return r.method
-}
-
-// GetDomain returns the registed domain which this route is ( if none, is "" which is means "localhost"/127.0.0.1)
-func (r Route) GetDomain() string {
- return r.domain
-}
-
-// GetPath returns the full registed path
-func (r Route) GetPath() string {
- return r.fullpath
-}
-
-// GetName returns the name of the route
-func (r Route) GetName() string {
- return r.name
-}
-
-// Name sets the route's name
-func (r *Route) Name(newName string) IRoute {
- r.name = newName
- return r
-}
-
-// GetMiddleware returns the chain of the []HandlerFunc registed to this Route
-func (r Route) GetMiddleware() Middleware {
- return r.middleware
-}
-
-// HasCors check if middleware passsed to a route has cors
-func (r *Route) HasCors() bool {
- return RouteConflicts(r, "httpmethod")
-}
-
-// ParsePath used to check arguments with the route's named parameters and return the correct url
-// if parse failed returns empty string
-func (r *Route) ParsePath(args ...interface{}) string {
- argsLen := len(args)
-
- // we have named parameters but arguments not given
- if argsLen == 0 && r.formattedParts > 0 {
- return ""
- }
-
- // we have arguments but they are much more than the named parameters
-
- // 1 check if we have /*, if yes then join all arguments to one as path and pass that as parameter
- if argsLen > r.formattedParts {
- if r.fullpath[len(r.fullpath)-1] == MatchEverythingByte {
- // we have to convert each argument to a string in this case
-
- argsString := make([]string, argsLen, argsLen)
-
- for i, v := range args {
- if s, ok := v.(string); ok {
- argsString[i] = s
- } else if num, ok := v.(int); ok {
- argsString[i] = strconv.Itoa(num)
- } else if b, ok := v.(bool); ok {
- argsString[i] = strconv.FormatBool(b)
- } else if arr, ok := v.([]string); ok {
- if len(arr) > 0 {
- argsString[i] = arr[0]
- argsString = append(argsString, arr[1:]...)
- }
- }
- }
-
- parameter := strings.Join(argsString, Slash)
- result := fmt.Sprintf(r.formattedPath, parameter)
- return result
- }
- // 2 if !1 return false
- return ""
- }
-
- arguments := args[0:]
-
- // check for arrays
- for i, v := range arguments {
- if arr, ok := v.([]string); ok {
- if len(arr) > 0 {
- interfaceArr := make([]interface{}, len(arr))
- for j, sv := range arr {
- interfaceArr[j] = sv
- }
- arguments[i] = interfaceArr[0]
- arguments = append(arguments, interfaceArr[1:]...)
- }
-
- }
- }
-
- return fmt.Sprintf(r.formattedPath, arguments...)
-}
-
-// ParseURI returns the subdomain+ host + ParsePath(...optional named parameters if route is dynamic)
-// returns an empty string if parse is failed
-func (r *Route) ParseURI(args ...interface{}) (uri string) {
- scheme := "http://"
- if r.station.server.IsSecure() {
- scheme = "https://"
- }
-
- host := r.station.server.VirtualHost()
- arguments := args[0:]
-
- // join arrays as arguments
- for i, v := range arguments {
- if arr, ok := v.([]string); ok {
- if len(arr) > 0 {
- interfaceArr := make([]interface{}, len(arr))
- for j, sv := range arr {
- interfaceArr[j] = sv
- }
- arguments[i] = interfaceArr[0]
- arguments = append(arguments, interfaceArr[1:]...)
- }
-
- }
- }
-
- // if it's dynamic subdomain then the first argument is the subdomain part
- if r.isWildcard() {
- if len(arguments) == 0 { // it's a wildcard subdomain but not arguments
- return
- }
-
- if subdomain, ok := arguments[0].(string); ok {
- host = subdomain + "." + host
- } else {
- // it is not array because we join them before. if not pass a string then this is not a subdomain part, return empty uri
- return
- }
-
- arguments = arguments[1:]
- }
-
- if parsedPath := r.ParsePath(arguments...); parsedPath != "" {
- uri = scheme + host + parsedPath
- }
-
- return
-
-}
-
-// RouteConflicts checks for route's middleware conflicts
-func RouteConflicts(r *Route, with string) bool {
- for _, h := range r.middleware {
- if m, ok := h.(interface {
- Conflicts() string
- }); ok {
- if c := m.Conflicts(); c == with {
- return true
- }
- }
- }
- return false
-}
diff --git a/router.go b/router.go
deleted file mode 100644
index bd3ed3bd..00000000
--- a/router.go
+++ /dev/null
@@ -1,308 +0,0 @@
-package iris
-
-import (
- "net/http/pprof"
- "strings"
- "sync"
-
- "github.com/kataras/iris/utils"
- "github.com/valyala/fasthttp"
-)
-
-const (
-
- // ParameterStartByte is very used on the node, it's just contains the byte for the ':' rune/char
- ParameterStartByte = byte(':')
- // SlashByte is just a byte of '/' rune/char
- SlashByte = byte('/')
- // Slash is just a string of "/"
- Slash = "/"
- // MatchEverythingByte is just a byte of '*" rune/char
- MatchEverythingByte = byte('*')
- // PrefixDynamicSubdomain is the prefix which dynamic subdomains are registed to, as virtual. Used internaly by Iris but good to know.
- PrefixDynamicSubdomain = "www.iris_subd0mAin.iris"
-
- // HTTP Methods(1)
-
- // MethodGet "GET"
- MethodGet = "GET"
- // MethodPost "POST"
- MethodPost = "POST"
- // MethodPut "PUT"
- MethodPut = "PUT"
- // MethodDelete "DELETE"
- MethodDelete = "DELETE"
- // MethodConnect "CONNECT"
- MethodConnect = "CONNECT"
- // MethodHead "HEAD"
- MethodHead = "HEAD"
- // MethodPatch "PATCH"
- MethodPatch = "PATCH"
- // MethodOptions "OPTIONS"
- MethodOptions = "OPTIONS"
- // MethodTrace "TRACE"
- MethodTrace = "TRACE"
-)
-
-var (
-
- // HTTP Methods(2)
-
- // MethodConnectBytes []byte("CONNECT")
- MethodConnectBytes = []byte(MethodConnect)
- // AllMethods "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", "PATCH", "OPTIONS", "TRACE"
- AllMethods = [...]string{"GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", "PATCH", "OPTIONS", "TRACE"}
-)
-
-// router internal is the route serving service, one router per server
-type router struct {
- *GardenParty
- *HTTPErrorContainer
- station *Iris
- garden *Garden
- methodMatch func(m1, m2 string) bool
- getRequestPath func(*fasthttp.RequestCtx) []byte
- // routes useful information, this info can be used to make custom links inside templates
- // the route's information (can be) changed after its registration
- lookups []IRoute
- ServeRequest func(reqCtx *fasthttp.RequestCtx)
- // errorPool is responsible to get the Context to handle not found errors
- errorPool sync.Pool
- //it's true when optimize already ran
- optimized bool
- mu sync.Mutex
-}
-
-// methodMatchCorsFunc is sets the methodMatch when cors enabled (look router.optimize), it's allowing OPTIONS method to all other methods except GET
-func methodMatchCorsFunc(m1, reqMethod string) bool {
- return m1 == reqMethod || reqMethod == MethodOptions //(m1 != MethodGet && reqMethod == MethodOptions)
-}
-
-// methodMatchFunc for normal method match
-func methodMatchFunc(m1, m2 string) bool {
- return m1 == m2
-}
-
-func getRequestPathDefault(reqCtx *fasthttp.RequestCtx) []byte {
- // default to escape then
- return reqCtx.Path()
-}
-
-// newRouter creates and returns an empty router
-func newRouter(station *Iris) *router {
- r := &router{
- station: station,
- garden: &Garden{},
- methodMatch: methodMatchFunc,
- getRequestPath: getRequestPathDefault,
- lookups: make([]IRoute, 0),
- HTTPErrorContainer: defaultHTTPErrors(),
- GardenParty: &GardenParty{relativePath: "/", station: station, root: true},
- errorPool: station.newContextPool()}
-
- r.ServeRequest = r.serveFunc
-
- return r
-
-}
-
-// addRoute is a middleware between router and garden
-// it just calls the garden's Plant method
-// is 'thread-safe'
-func (r *router) addRoute(route IRoute) {
- r.mu.Lock()
- defer r.mu.Unlock()
- r.lookups = append(r.lookups, route)
- r.garden.Plant(r.station, route)
-}
-
-// RouteByName returns a route by its name,if not found then returns a route with empty path
-// Note that the searching is case-sensitive
-func (r *router) RouteByName(routeName string) IRoute {
- for _, route := range r.lookups {
- if route.GetName() == routeName {
- return route
- }
- }
- return &Route{}
-}
-
-// UriOf returns the parsed URI of a route
-// receives two parameters
-// the first is the route's name (string)
-// the second is a variadic, if the route is dynamic (receives named parameters) then pass the value of these parameters here
-// overview of the result is: scheme(http or https if ListenTLS)/yourhost.com:PORT/profile/theusername/friends/theid
-//
-// example /profile/:username/friends/:friendId with name "profile" -> .UriOf("profile","kataras",8) will give http://127.0.0.1:8080/profile/kataras/friends/8
-func (r *router) UriOf(routeName string, args ...interface{}) (string, error) {
- route := r.RouteByName(routeName)
- // check if not found
- if route.GetMethod() == "" {
- return "", ErrRenderRouteNotFound.Format(routeName)
- }
-
- return route.ParseURI(args...), nil
-}
-
-//check if any tree has cors setted to true, means that cors middleware is added
-func (r *router) cors() (has bool) {
- r.garden.visitAll(func(i int, tree *tree) {
- if tree.cors {
- has = true
- }
- })
- return
-}
-
-// check if any tree has subdomains
-func (r *router) hosts() (has bool) {
- r.garden.visitAll(func(i int, tree *tree) {
- if tree.hosts {
- has = true
- }
- })
- return
-}
-
-// optimize runs once before listen, it checks if cors or hosts enabled and make the necessary changes to the Router itself
-func (r *router) optimize() {
- if r.optimized {
- return
- }
-
- if r.cors() {
- r.methodMatch = methodMatchCorsFunc
- }
-
- // For performance only,in order to not check at runtime for hosts and subdomains, I think it's better to do this:
- if r.hosts() {
- r.ServeRequest = r.serveDomainFunc
- }
-
- //if PathEscape disabled, then take the raw URI
- if r.station.config.DisablePathEscape {
- r.getRequestPath = func(reqCtx *fasthttp.RequestCtx) []byte {
- // RequestURI fixes the https://github.com/kataras/iris/issues/135
- return reqCtx.RequestURI()
- }
- }
-
- // set the debug profiling handlers if Profile enabled, before the server startup, not earlier
- if r.station.config.Profile && r.station.config.ProfilePath != "" {
- debugPath := r.station.config.ProfilePath
-
- htmlMiddleware := func(ctx *Context) {
- ctx.SetContentType(ContentHTML + r.station.rest.CompiledCharset)
- ctx.Next()
- }
-
- indexHandler := ToHandlerFunc(pprof.Index)
- cmdlineHandler := ToHandlerFunc(pprof.Cmdline)
- profileHandler := ToHandlerFunc(pprof.Profile)
- symbolHandler := ToHandlerFunc(pprof.Symbol)
-
- goroutineHandler := ToHandlerFunc(pprof.Handler("goroutine"))
- heapHandler := ToHandlerFunc(pprof.Handler("heap"))
- threadcreateHandler := ToHandlerFunc(pprof.Handler("threadcreate"))
- debugBlockHandler := ToHandlerFunc(pprof.Handler("block"))
-
- r.Get(debugPath+"/*action", htmlMiddleware, func(ctx *Context) {
- action := ctx.Param("action")
- if len(action) > 1 {
- if strings.Contains(action, "cmdline") {
- cmdlineHandler.Serve((ctx))
- } else if strings.Contains(action, "profile") {
- profileHandler.Serve(ctx)
- } else if strings.Contains(action, "symbol") {
- symbolHandler.Serve(ctx)
- } else if strings.Contains(action, "goroutine") {
- goroutineHandler.Serve(ctx)
- } else if strings.Contains(action, "heap") {
- heapHandler.Serve(ctx)
- } else if strings.Contains(action, "threadcreate") {
- threadcreateHandler.Serve(ctx)
- } else if strings.Contains(action, "debug/block") {
- debugBlockHandler.Serve(ctx)
- }
- } else {
- indexHandler.Serve(ctx)
- }
-
- })
-
- }
-
- r.optimized = true
-}
-
-// notFound internal method, it justs takes the context from pool ( in order to have the custom errors available) and procedure a Not Found 404 error
-// this is being called when no route was found used on the ServeRequest.
-func (r *router) notFound(reqCtx *fasthttp.RequestCtx) {
- ctx := r.errorPool.Get().(*Context)
- ctx.Reset(reqCtx)
- ctx.NotFound()
- r.errorPool.Put(ctx)
-}
-
-//************************************************************************************
-// serveFunc & serveDomainFunc selected on router.optimize, which runs before station's listen
-// they are not used directly.
-//************************************************************************************
-
-// serve finds and serves a route by it's request context
-// If no route found, it sends an http status 404
-func (r *router) serveFunc(reqCtx *fasthttp.RequestCtx) {
- method := utils.BytesToString(reqCtx.Method())
- tree := r.garden.first
- path := utils.BytesToString(r.getRequestPath(reqCtx))
- for tree != nil {
- if r.methodMatch(tree.method, method) {
- if !tree.serve(reqCtx, path) {
- r.notFound(reqCtx)
- }
- return
- }
- tree = tree.next
- }
- //not found, get the first's pool and use that to send a custom http error(if setted)
-
- r.notFound(reqCtx)
-
-}
-
-// serveDomainFunc finds and serves a domain tree's route by it's request context
-// If no route found, it sends an http status 404
-func (r *router) serveDomainFunc(reqCtx *fasthttp.RequestCtx) {
- method := utils.BytesToString(reqCtx.Method())
- host := utils.BytesToString(reqCtx.Host())
- fulldomain := ""
- if strings.Count(host, ".") >= 2 && host != r.station.server.Host() {
- if portIdx := strings.Index(host, ":"); portIdx != -1 {
- fulldomain = host[0:portIdx]
- } else {
- fulldomain = host
- }
- }
-
- path := utils.BytesToString(r.getRequestPath(reqCtx))
- tree := r.garden.first
- for tree != nil {
- if tree.hosts && tree.domain != "" && fulldomain != "" {
- if tree.domain == fulldomain { // it's a static subdomain
- path = fulldomain + path
- } else if strings.Index(tree.domain, PrefixDynamicSubdomain) != -1 { // it's a dynamic virtual subdomain
- path = PrefixDynamicSubdomain + path
- }
- }
-
- if r.methodMatch(tree.method, method) {
- if tree.serve(reqCtx, path) {
- return
- }
- }
- tree = tree.next
- }
-
- //not found, get the first's pool and use that to send a custom http error(if setted)
- r.notFound(reqCtx)
-}
diff --git a/server/README.md b/server/README.md
deleted file mode 100644
index b3c57301..00000000
--- a/server/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-## Package information
-
-I decide to split the whole server from the main iris package because these files don't depends on any of the iris' types.
-
-
-**That's it.**
diff --git a/server/errors.go b/server/errors.go
deleted file mode 100644
index 31333fa6..00000000
--- a/server/errors.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package server
-
-import "github.com/kataras/iris/errors"
-
-var (
- // ErrServerPortAlreadyUsed returns an error with message: 'Server can't run, port is already used'
- ErrServerPortAlreadyUsed = errors.New("Server can't run, port is already used")
- // ErrServerAlreadyStarted returns an error with message: 'Server is already started and listening'
- ErrServerAlreadyStarted = errors.New("Server is already started and listening")
- // ErrServerOptionsMissing returns an error with message: 'You have to pass iris.ServerOptions'
- ErrServerOptionsMissing = errors.New("You have to pass iris.ServerOptions")
- // ErrServerTLSOptionsMissing returns an error with message: 'You have to set CertFile and KeyFile to iris.ServerOptions before ListenTLS'
- ErrServerTLSOptionsMissing = errors.New("You have to set CertFile and KeyFile to iris.ServerOptions before ListenTLS")
- // ErrServerIsClosed returns an error with message: 'Can't close the server, propably is already closed or never started'
- ErrServerIsClosed = errors.New("Can't close the server, propably is already closed or never started")
- // ErrServerUnknown returns an error with message: 'Unknown reason from Server, please report this as bug!'
- ErrServerUnknown = errors.New("Unknown reason from Server, please report this as bug!")
- // ErrParsedAddr returns an error with message: 'ListeningAddr error, for TCP and UDP, the syntax of ListeningAddr is host:port, like 127.0.0.1:8080.
- // If host is omitted, as in :8080, Listen listens on all available interfaces instead of just the interface with the given host address.
- // See Dial for more details about address syntax'
- ErrParsedAddr = errors.New("ListeningAddr error, for TCP and UDP, the syntax of ListeningAddr is host:port, like 127.0.0.1:8080. If host is omitted, as in :8080, Listen listens on all available interfaces instead of just the interface with the given host address. See Dial for more details about address syntax")
- // ErrServerRemoveUnix returns an error with message: 'Unexpected error when trying to remove unix socket file +filename: +specific error"'
- ErrServerRemoveUnix = errors.New("Unexpected error when trying to remove unix socket file. Addr: %s | Trace: %s")
- // ErrServerChmod returns an error with message: 'Cannot chmod +mode for +host:+specific error
- ErrServerChmod = errors.New("Cannot chmod %#o for %q: %s")
-)
diff --git a/server/server.go b/server/server.go
deleted file mode 100644
index 6a1966be..00000000
--- a/server/server.go
+++ /dev/null
@@ -1,248 +0,0 @@
-package server
-
-import (
- "net"
- "os"
- "strings"
-
- "github.com/kataras/iris/config"
- "github.com/valyala/fasthttp"
-)
-
-// Server is the IServer's implementation, holds the fasthttp's Server, a net.Listener, the ServerOptions, and the handler
-// handler is registed at the Station/Iris level
-type Server struct {
- *fasthttp.Server
- listener net.Listener
- Config config.Server
- started bool
- tls bool
- handler fasthttp.RequestHandler
-}
-
-// New returns a pointer to a Server object, and set it's options if any, nothing more
-func New(cfg ...config.Server) *Server {
- c := config.DefaultServer().Merge(cfg)
- s := &Server{Server: &fasthttp.Server{Name: config.ServerName}, Config: c}
-
- s.Config.ListeningAddr = parseAddr(s.Config.ListeningAddr)
-
- return s
-}
-
-// SetHandler sets the handler in order to listen on new requests, this is done at the Station/Iris level
-func (s *Server) SetHandler(h fasthttp.RequestHandler) {
- s.handler = h
- if s.Server != nil {
- s.Server.Handler = s.handler
- }
-}
-
-// Handler returns the fasthttp.RequestHandler which is registed to the Server
-func (s *Server) Handler() fasthttp.RequestHandler {
- return s.handler
-}
-
-// IsListening returns true if server is listening/started, otherwise false
-func (s *Server) IsListening() bool {
- return s.started
-}
-
-// IsSecure returns true if server uses TLS, otherwise false
-func (s *Server) IsSecure() bool {
- return s.tls
-}
-
-// Listener returns the net.Listener which this server (is) listening to
-func (s *Server) Listener() net.Listener {
- return s.listener
-}
-
-// Host returns the Listener().Addr().String(), if server is not listening it returns the config.ListeningAddr
-func (s *Server) Host() (host string) {
- if s.IsListening() {
- return s.Listener().Addr().String()
- } else {
- return s.Config.ListeningAddr
- }
-}
-
-// VirtualHost returns the s.Config.ListeningAddr, if host provided else returns the Listener's (Host())
-//
-// Note: currently this is used only on iris/route.ParseURI.
-//
-func (s *Server) VirtualHost() (host string) {
- // we always have at least the :PORT because of parseAddr, so we just
- // check if we have anything before PORT
- a := s.Config.ListeningAddr
- if len(a[0:strings.IndexByte(a, ':')]) > 0 {
- return a
- } else {
- return s.Host()
- }
-}
-
-// Hostname returns the hostname part only, if host == 0.0.0.0:8080 it will return the 0.0.0.0
-// if server is not listening it returns the config.ListeningAddr's hostname part
-func (s *Server) Hostname() (hostname string) {
- if s.IsListening() {
- fullhost := s.Listener().Addr().String()
- hostname = fullhost[0:strings.IndexByte(fullhost, ':')] // no the port
- } else {
- fullhost := s.Config.ListeningAddr
- if idx := strings.IndexByte(fullhost, ':'); idx > 1 { // at least after second char
- hostname = hostname[0:idx]
- } else {
- hostname = "0.0.0.0"
- }
-
- }
- return
-}
-
-//Serve just serves a listener, it is a blocking action, plugin.PostListen is not fired here.
-func (s *Server) Serve(l net.Listener) error {
- s.listener = l
- return s.Server.Serve(l)
-}
-
-// listen starts the process of listening to the new requests
-func (s *Server) listen() (err error) {
-
- if s.started {
- err = ErrServerAlreadyStarted.Return()
- return
- }
- s.listener, err = net.Listen("tcp4", s.Config.ListeningAddr)
-
- if err != nil {
- err = ErrServerPortAlreadyUsed.Return()
- return
- }
-
- //Non-block way here because I want the plugin's PostListen ability...
- go s.Server.Serve(s.listener)
-
- s.started = true
- s.tls = false
-
- return
-}
-
-// listenTLS starts the process of listening to the new requests using TLS, keyfile and certfile are given before this method fires
-func (s *Server) listenTLS() (err error) {
-
- if s.started {
- err = ErrServerAlreadyStarted.Return()
- return
- }
-
- if s.Config.CertFile == "" || s.Config.KeyFile == "" {
- err = ErrServerTLSOptionsMissing.Return()
- return
- }
-
- s.listener, err = net.Listen("tcp4", s.Config.ListeningAddr)
-
- if err != nil {
- err = ErrServerPortAlreadyUsed.Return()
- return
- }
-
- go s.Server.ServeTLS(s.listener, s.Config.CertFile, s.Config.KeyFile)
-
- s.started = true
- s.tls = true
-
- return
-}
-
-// listenUnix starts the process of listening to the new requests using a 'socket file', this works only on unix
-func (s *Server) listenUnix() (err error) {
-
- if s.started {
- err = ErrServerAlreadyStarted.Return()
- return
- }
-
- mode := s.Config.Mode
-
- //this code is from fasthttp ListenAndServeUNIX, I extracted it because we need the tcp.Listener
- if errOs := os.Remove(s.Config.ListeningAddr); errOs != nil && !os.IsNotExist(errOs) {
- err = ErrServerRemoveUnix.Format(s.Config.ListeningAddr, errOs.Error())
- return
- }
- //
- s.listener, err = net.Listen("unix", s.Config.ListeningAddr)
-
- if err != nil {
- err = ErrServerPortAlreadyUsed.Return()
- return
- }
-
- if err = os.Chmod(s.Config.ListeningAddr, mode); err != nil {
- err = ErrServerChmod.Format(mode, s.Config.ListeningAddr, err.Error())
- return
- }
-
- s.Server.Handler = s.handler
- go s.Server.Serve(s.listener)
-
- s.started = true
- s.tls = false
-
- return
-
-}
-
-// OpenServer opens/starts/runs/listens (to) the server, listenTLS if Cert && Key is registed, listenUnix if Mode is registed, otherwise listen
-// instead of return an error this is panics on any server's error
-func (s *Server) OpenServer() (err error) {
- if s.Config.CertFile != "" && s.Config.KeyFile != "" {
- err = s.listenTLS()
- } else if s.Config.Mode > 0 {
- err = s.listenUnix()
- } else {
- err = s.listen()
- }
-
- return
-}
-
-// CloseServer closes the server
-func (s *Server) CloseServer() error {
-
- if !s.started {
- return ErrServerIsClosed.Return()
- }
-
- if s.listener != nil {
- s.started = false
- return s.listener.Close()
- }
- return nil
-}
-
-// parseAddr gets a slice of string and returns the address of which the Iris' server can listen
-func parseAddr(fullHostOrPort ...string) string {
-
- if len(fullHostOrPort) > 1 {
- fullHostOrPort = fullHostOrPort[0:1]
- }
- addr := config.DefaultServerAddr // default address
- // if nothing passed, then use environment's port (if any) or just :8080
- if len(fullHostOrPort) == 0 {
- if envPort := os.Getenv("PORT"); len(envPort) > 0 {
- addr = ":" + envPort
- }
-
- } else if len(fullHostOrPort) == 1 {
- addr = fullHostOrPort[0]
- if strings.IndexRune(addr, ':') == -1 {
- //: doesn't found on the given address, so maybe it's only a port
- addr = ":" + addr
- }
- }
-
- return addr
-}
diff --git a/sessions/providers/memory/register.go b/sessions/providers/memory/register.go
index ba61e718..cc2c998c 100644
--- a/sessions/providers/memory/register.go
+++ b/sessions/providers/memory/register.go
@@ -21,7 +21,6 @@ var (
func register() {
// the actual work is here.
Provider.NewStore = func(sessionId string, cookieLifeDuration time.Duration) store.IStore {
- //println("memory.go:49-> requesting new memory store with sessionid: " + sessionId)
return &Store{sid: sessionId, lastAccessedTime: time.Now(), values: make(map[interface{}]interface{}, 0)}
}
sessions.Register(Provider)
diff --git a/sessions/sessions.go b/sessions/sessions.go
index bd8a8142..ba347949 100644
--- a/sessions/sessions.go
+++ b/sessions/sessions.go
@@ -4,7 +4,12 @@ import "github.com/kataras/iris/config"
// New creates & returns a new Manager and start its GC
func New(cfg ...config.Sessions) *Manager {
- manager, err := newManager(config.DefaultSessions().Merge(cfg))
+ c := config.DefaultSessions().Merge(cfg)
+ // If provider is empty then return nil manager, means that the sessions are disabled
+ if c.Provider == "" {
+ return nil
+ }
+ manager, err := newManager(c)
if err != nil {
panic(err.Error()) // we have to panic here because we will start GC after and if provider is nil then many panics will come
}
diff --git a/tests/httperror_test.go b/tests/httperror_test.go
index 2808ab45..b9a82daa 100644
--- a/tests/httperror_test.go
+++ b/tests/httperror_test.go
@@ -6,7 +6,6 @@ import (
"github.com/gavv/httpexpect"
"github.com/gavv/httpexpect/fasthttpexpect"
"github.com/kataras/iris"
- "github.com/kataras/iris/config"
)
var notFoundMessage = "Iris custom message for 404 not found"
@@ -46,15 +45,7 @@ func TestCustomErrors(t *testing.T) {
}
}
- api.PreListen(config.Server{ListeningAddr: ""})
-
- // create httpexpect instance that will call fasthtpp.RequestHandler directly
- e := httpexpect.WithConfig(httpexpect.Config{
- Reporter: httpexpect.NewAssertReporter(t),
- Client: fasthttpexpect.NewBinder(api.ServeRequest),
- })
- // first register the custom errors
-
+ // register the custom errors
api.OnError(404, func(ctx *iris.Context) {
ctx.Write("%s", notFoundMessage)
})
@@ -63,6 +54,12 @@ func TestCustomErrors(t *testing.T) {
ctx.Write("%s", internalServerMessage)
})
+ // create httpexpect instance that will call fasthtpp.RequestHandler directly
+ e := httpexpect.WithConfig(httpexpect.Config{
+ Reporter: httpexpect.NewAssertReporter(t),
+ Client: fasthttpexpect.NewBinder(api.NoListen().Handler),
+ })
+
// run the tests
for _, r := range routesCustomErrors {
e.Request(r.Method, r.RequestPath).
diff --git a/tests/party_test.go b/tests/party_test.go
new file mode 100644
index 00000000..b5a582e8
--- /dev/null
+++ b/tests/party_test.go
@@ -0,0 +1,53 @@
+package tests
+
+import (
+ "testing"
+
+ "github.com/gavv/httpexpect"
+ "github.com/gavv/httpexpect/fasthttpexpect"
+ "github.com/kataras/iris"
+)
+
+func TestSimpleParty(t *testing.T) {
+ h := func(c *iris.Context) { c.WriteString(c.HostString() + c.PathString()) }
+
+ /*
+ // subdomain first, but this test will fail on your machine, so I just commend it, you can imagine what will be
+ party2 := iris.Party("kataras.")
+ {
+ party2.Get("/", h)
+ party2.Get("/path1", h)
+ party2.Get("/path2", h)
+ party2.Get("/namedpath/:param1/something/:param2", h)
+ party2.Get("/namedpath/:param1/something/:param2/else", h)
+ }*/
+
+ // simple
+ party1 := iris.Party("/party1")
+ {
+ party1.Get("/", h)
+ party1.Get("/path1", h)
+ party1.Get("/path2", h)
+ party1.Get("/namedpath/:param1/something/:param2", h)
+ party1.Get("/namedpath/:param1/something/:param2/else", h)
+ }
+
+ // create httpexpect instance that will call fasthtpp.RequestHandler directly
+ e := httpexpect.WithConfig(httpexpect.Config{
+ Reporter: httpexpect.NewAssertReporter(t),
+ Client: fasthttpexpect.NewBinder(iris.NoListen().Handler),
+ })
+
+ request := func(reqPath string) {
+ e.Request("GET", reqPath).
+ Expect().
+ Status(iris.StatusOK).Body().Equal(reqPath)
+ }
+
+ // run the tests
+ request("/party1/")
+ request("/party1/path1")
+ request("/party1/path2")
+ request("/party1/namedpath/theparam1/something/theparam2")
+ request("/party1/namedpath/theparam1/something/theparam2/else")
+}
diff --git a/tests/router_test.go b/tests/route_test.go
similarity index 94%
rename from tests/router_test.go
rename to tests/route_test.go
index 04e6e9fa..02a2ebfe 100644
--- a/tests/router_test.go
+++ b/tests/route_test.go
@@ -7,7 +7,6 @@ import (
"github.com/gavv/httpexpect"
"github.com/gavv/httpexpect/fasthttpexpect"
"github.com/kataras/iris"
- "github.com/kataras/iris/config"
)
type param struct {
@@ -59,7 +58,6 @@ var routes = []route{
func TestRouter(t *testing.T) {
api := iris.New()
-
for idx := range routes {
r := routes[idx]
if r.Register {
@@ -88,11 +86,10 @@ func TestRouter(t *testing.T) {
}
}
- api.PreListen(config.Server{ListeningAddr: ""})
// create httpexpect instance that will call fasthtpp.RequestHandler directly
e := httpexpect.WithConfig(httpexpect.Config{
Reporter: httpexpect.NewAssertReporter(t),
- Client: fasthttpexpect.NewBinder(api.ServeRequest),
+ Client: fasthttpexpect.NewBinder(api.NoListen().Handler),
})
// run the tests (1)
@@ -107,15 +104,14 @@ func TestRouter(t *testing.T) {
func TestPathEscape(t *testing.T) {
api := iris.New()
+
api.Get("/details/:name", func(ctx *iris.Context) {
name := ctx.Param("name")
highlight := ctx.URLParam("highlight")
ctx.Text(iris.StatusOK, fmt.Sprintf("name=%s,highlight=%s", name, highlight))
})
- api.PreListen(config.Server{ListeningAddr: ""})
- api.PostListen()
- e := httpexpect.WithConfig(httpexpect.Config{Reporter: httpexpect.NewAssertReporter(t), Client: fasthttpexpect.NewBinder(api.ServeRequest)})
+ e := httpexpect.WithConfig(httpexpect.Config{Reporter: httpexpect.NewAssertReporter(t), Client: fasthttpexpect.NewBinder(api.NoListen().Handler)})
e.Request("GET", "/details/Sakamoto desu ga?highlight=text").Expect().Status(iris.StatusOK).Body().Equal("name=Sakamoto desu ga,highlight=text")
}
diff --git a/tree.go b/tree.go
deleted file mode 100644
index 7e162892..00000000
--- a/tree.go
+++ /dev/null
@@ -1,145 +0,0 @@
-package iris
-
-import (
- "bytes"
- "sync"
-
- "github.com/kataras/iris/utils"
- "github.com/valyala/fasthttp"
-)
-
-type (
- tree struct {
- station *Iris
- method string
- rootBranch *Branch
- domain string
- hosts bool //if domain != "" we set it directly on .Plant
- cors bool // if cross domain allow enabled
- pool sync.Pool
- next *tree
- }
-
- // Garden is the main area which routes are planted/placed
- Garden struct {
- first *tree
- }
-)
-
-// garden
-
-func (g *Garden) visitAll(f func(i int, tr *tree)) {
- t := g.first
- i := 0
- for t != nil {
-
- f(i, t)
- t = t.next
- }
-}
-
-// visitAllBreak like visitAll but if true to the function then it breaks
-func (g *Garden) visitAllBreak(f func(i int, tr *tree) bool) {
- t := g.first
- i := 0
- for t != nil {
-
- if f(i, t) {
- break
- }
- t = t.next
- }
-}
-
-func (g *Garden) last() (t *tree) {
-
- t = g.first
- for t.next != nil {
- t = t.next
- }
- return
-}
-
-// getRootByMethodAndDomain returns the correct branch which it's method&domain is equal to the given method&domain, from a garden's tree
-// trees with no domain means that their domain==""
-func (g *Garden) getRootByMethodAndDomain(method string, domain string) (b *Branch) {
- g.visitAll(func(i int, t *tree) {
- if t.domain == domain && t.method == method {
- b = t.rootBranch
- }
- })
-
- return
-}
-
-// Plant plants/adds a route to the garden
-func (g *Garden) Plant(station *Iris, _route IRoute) {
- method := _route.GetMethod()
- domain := _route.GetDomain()
- path := _route.GetPath()
- theRoot := g.getRootByMethodAndDomain(method, domain)
- if theRoot == nil {
- theRoot = new(Branch)
- theNewTree := newTree(station, method, theRoot, domain, len(domain) > 0, _route.HasCors())
- if g.first == nil {
- g.first = theNewTree
- } else {
- g.last().next = theNewTree
- }
-
- }
- theRoot.AddBranch(domain+path, _route.GetMiddleware())
-}
-
-// tree
-
-func newTree(station *Iris, method string, theRoot *Branch, domain string, hosts bool, hasCors bool) *tree {
- t := &tree{station: station, method: method, rootBranch: theRoot, domain: domain, hosts: hosts, cors: hasCors, pool: station.newContextPool()}
- return t
-}
-
-// serve serves the route
-func (_tree *tree) serve(reqCtx *fasthttp.RequestCtx, path string) bool {
- ctx := _tree.pool.Get().(*Context)
- ctx.Reset(reqCtx)
- middleware, params, mustRedirect := _tree.rootBranch.GetBranch(path, ctx.Params) // pass the parameters here for 0 allocation
- if middleware != nil {
- ctx.Params = params
- ctx.middleware = middleware
- //ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent)
- ctx.Do()
- _tree.pool.Put(ctx)
- return true
- } else if mustRedirect && !_tree.station.config.DisablePathCorrection && !bytes.Equal(reqCtx.Method(), MethodConnectBytes) {
-
- reqPath := path
- pathLen := len(reqPath)
-
- if pathLen > 1 {
-
- if reqPath[pathLen-1] == '/' {
- reqPath = reqPath[:pathLen-1] //remove the last /
- } else {
- //it has path prefix, it doesn't ends with / and it hasn't be found, then just add the slash
- reqPath = reqPath + "/"
- }
-
- ctx.Request.URI().SetPath(reqPath)
- urlToRedirect := utils.BytesToString(ctx.Request.RequestURI())
-
- ctx.Redirect(urlToRedirect, 301) // StatusMovedPermanently
- // RFC2616 recommends that a short note "SHOULD" be included in the
- // response because older user agents may not understand 301/307.
- // Shouldn't send the response for POST or HEAD; that leaves GET.
- if _tree.method == MethodGet {
- note := "Moved Permanently.\n"
- ctx.Write(note)
- }
- _tree.pool.Put(ctx)
- return true
- }
- }
-
- _tree.pool.Put(ctx)
- return false
-}
diff --git a/utils/README.md b/utils/README.md
index e82e25aa..99e37f94 100644
--- a/utils/README.md
+++ b/utils/README.md
@@ -1,6 +1,6 @@
## Package information
-This package contains helpful functions that iris uses, you can use them to your project also!
+This package contains helpful functions that iris, internally, uses
**That's it.**
diff --git a/websocket/server.go b/websocket/server.go
index 8c3bed60..274312d0 100644
--- a/websocket/server.go
+++ b/websocket/server.go
@@ -20,6 +20,8 @@ type (
Upgrade(context.IContext) error
// OnConnection registers a callback which fires when a connection/client is connected to the server
OnConnection(ConnectionFunc)
+ // Config returns a pointer to server's configs
+ Config() *config.Websocket
}
// roomPayload is used as payload from the connection to the server
@@ -57,9 +59,10 @@ var _ Server = &server{}
// server implementation
-func newServer(c config.Websocket) *server {
+// newServer creates a websocket server and returns it
+func newServer(c *config.Websocket) *server {
s := &server{
- config: &c,
+ config: c,
put: make(chan *connection),
free: make(chan *connection),
connections: make(map[string]*connection),
@@ -72,10 +75,13 @@ func newServer(c config.Websocket) *server {
s.upgrader = websocket.New(s.handleConnection)
go s.serve() // start the server automatically
-
return s
}
+func (s *server) Config() *config.Websocket {
+ return s.config
+}
+
func (s *server) Upgrade(ctx context.IContext) error {
return s.upgrader.Upgrade(ctx)
}
diff --git a/websocket/websocket.go b/websocket/websocket.go
index 8b1550d1..dc7e31b9 100644
--- a/websocket/websocket.go
+++ b/websocket/websocket.go
@@ -9,9 +9,8 @@ import (
// to avoid the import cycle to /kataras/iris. The ws package is used inside iris' station configuration
// inside Iris' configuration like kataras/iris/sessions, kataras/iris/render/rest, kataras/iris/render/template, kataras/iris/server and so on.
type irisStation interface {
- H_(string, string, func(context.IContext))
- StaticContent(string, string, []byte)
- Logger() *logger.Logger
+ H_(string, string, func(context.IContext)) func(string)
+ StaticContent(string, string, []byte) func(string)
}
//
@@ -22,16 +21,34 @@ type irisStation interface {
// This is not usable for you, unless you need more than one websocket server,
// because iris' station already has one which you can configure and start
//
-func New(station irisStation, cfg ...config.Websocket) Server {
- c := config.DefaultWebsocket().Merge(cfg)
+// This is deprecated after rc-1, now we create the server and after register it
+// because I want to be able to call the Websocket via a property and no via func before iris.Listen.
+func New(station irisStation, c *config.Websocket, logger *logger.Logger) Server {
if c.Endpoint == "" {
- station.Logger().Panicf("Websockets - config's Endpoint is empty, you have to set it in order to enable and start the websocket server!!. Refer to the docs if you can't figure out.")
+ //station.Logger().Panicf("Websockets - config's Endpoint is empty, you have to set it in order to enable and start the websocket server!!. Refer to the docs if you can't figure out.")
+ return nil
}
server := newServer(c)
+ RegisterServer(station, server, logger)
+ return server
+}
+
+// NewServer creates a websocket server and returns it
+func NewServer(c *config.Websocket) Server {
+ return newServer(c)
+}
+
+// RegisterServer registers the handlers for the websocket server
+// it's a bridge between station and websocket server
+func RegisterServer(station irisStation, server Server, logger *logger.Logger) {
+ c := server.Config()
+ if c.Endpoint == "" {
+ return
+ }
websocketHandler := func(ctx context.IContext) {
if err := server.Upgrade(ctx); err != nil {
- station.Logger().Panic(err)
+ logger.Panic(err)
}
}
@@ -42,7 +59,7 @@ func New(station irisStation, cfg ...config.Websocket) Server {
}
if err := server.Upgrade(ctx); err != nil {
- station.Logger().Panic(err)
+ logger.Panic(err)
}
}
}
@@ -51,7 +68,6 @@ func New(station irisStation, cfg ...config.Websocket) Server {
// serve the client side on domain:port/iris-ws.js
station.StaticContent("/iris-ws.js", "application/json", clientSource)
- return server
}
var clientSource = []byte(`var stringMessageType = 0;