Update to 3.0.0-rc.1 - Read the HISTORY.md. Relative: #183 #184 #166 #176 #181

Read https://github.com/kataras/iris/tree/master/HISTORY.md
This commit is contained in:
Makis Maropoulos 2016-06-14 08:45:40 +03:00
parent 2da67206c8
commit d837381b16
59 changed files with 3972 additions and 4927 deletions

View File

@ -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.

View File

@ -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

View File

@ -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")
)

View File

@ -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))
}

View File

@ -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
}

View File

@ -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
}

457
branch.go
View File

@ -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
}

View File

@ -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(),
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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...)
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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!")
)

View File

@ -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

View File

@ -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 {

View File

@ -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
}

1303
http.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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)
}

193
initiatory.go Normal file
View File

@ -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()
}

1379
iris.go

File diff suppressed because it is too large Load Diff

View File

@ -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()
}

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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)
}

642
party.go
View File

@ -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
}

376
plugin.go
View File

@ -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()
}
}
}

View File

@ -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){})

View File

@ -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 {

View File

@ -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")
}

View File

@ -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),
})

View File

@ -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...

View File

@ -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()
}

View File

@ -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()
}
}

View File

@ -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")
}
```

View File

@ -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{}
}

View File

@ -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){})

View File

@ -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()
}

View File

@ -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 {

View File

@ -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, "<b> Iris </b> <br/> Templates are disabled via config.NoEngine, check your iris' configuration please.")
ctx.HTML(403, "<b> Iris </b> <br/> 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")
}

280
route.go
View File

@ -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
}

308
router.go
View File

@ -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)
}

View File

@ -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.**

View File

@ -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")
)

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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).

53
tests/party_test.go Normal file
View File

@ -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")
}

View File

@ -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")
}

145
tree.go
View File

@ -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 := "<a href=\"" + utils.HTMLEscape(urlToRedirect) + "\">Moved Permanently</a>.\n"
ctx.Write(note)
}
_tree.pool.Put(ctx)
return true
}
}
_tree.pool.Put(ctx)
return false
}

View File

@ -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.**

View File

@ -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)
}

View File

@ -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;