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;