20 days of unstoppable work. Waiting fo go 1.8, I didn't finish yet, some touches remains.

Former-commit-id: ed84f99c89f43fe5e980a8e6d0ee22c186f0e1b9
This commit is contained in:
Gerasimos (Makis) Maropoulos 2017-02-14 05:54:11 +02:00
parent 2b2a205e63
commit 244a59e055
108 changed files with 9016 additions and 7596 deletions

View File

@ -1,4 +1,4 @@
- Version : **6.1.2**
- Version : **6.2.0**
- Operating System:

View File

@ -1 +1,62 @@
If you are interested in contributing to the Iris project, please see the document [CONTRIBUTING](https://github.com/kataras/iris/blob/master/.github/CONTRIBUTING.md).
##### Note that I do not accept pull requests here and that I use the issue tracker for bug reports and proposals only. Please ask questions on the [https://kataras.rocket.chat/channel/iris][Chat] or [http://stackoverflow.com/](http://stackoverflow.com).
> **it's not personal to somebody specific**, I don't want to attack to anyone, I am polite but I had to write these complaints, eventually.
I wanted to answer on some users who complaining that I'm not accepting PRs to the main code base.
I am accepting PRs to the [iris-contrib](https://github.com/iris-contrib) and I see how things were gone.
As you already know I'm not a native english speaker, the gitbook PRs were **all excellent and I thanks you from my deep of my heart!**. My complain and my personal experience with PRs
you will read below is a fact for all repositories **except the gitbook's PRs and zaplogger middleware**.
There weren't many PRs but anyway...some people 'attacking' me because I code all alone, this is what it is.
Let's take for example the `cors` middleware.
Yes that middleware which didn't worked for you and the 80% of the overral middleware's issues were for this and only this middleware.
The guy who did the first PR to the cors middleware didn't answer to your issues, I had to do it that,
I had to answer for another's code.
It's true that I don't like these situations and this is the reason why I'm always avoid of accepting PRs to the main iris codebase.
Do you imagine what would happen if I was accepting PRs here, to the main iris repository?... I don't.
Not because the community sucks and they are not good as me, maybe they are better but they are not **responsible** for their code
and I'm sorry for telling you that but this is what I received as an experience of letting people to 'help' at the beggining:
better safe and 'alone' rather than a transformation of the framework to a collection of big 'anarchy' and unnecessary features.
**Let's explain** what I mean by 'anarchy':
Some github users made PRs without even posting a new github issue to describe the idea or the bug before push their code,
surprisingly 100% of the changes to these PRs were for things that already implemented by
the framework but they had no clue because they didn't care or didn't have time to view the code closer, they just wanted to make an one-line PR and disappear.
We don't have any guarantee that an author of any PR will be here to answer to users or to fix a bug or
to make changes on future iris changes(and as ultimately this proved most of them they didn't care about your questions, your bug reports and you). **I'm responsible to individual developers and companies who using Iris to their projects.**
As you already noticed I'm online almost all the day to answer to your questions (almost instantly), fixing bugs and upgrading the code.
**This is why this Framework is not like others, it has the abiliy to be always synced with the latest trends and features happens around the world.**
Yes, I did disappointed by a small group of the community, I was waiting for more activity, I was imaging big work all together and the open-source idea.
After the results I came up, I had to pause this 'idea' on my mind for a very long time.
This is why I never ask you for a PR here, in the main repository, your contribution can be 'only' by
reporting bugs and feature requests, **these are not a small part, things like these made this framework so ideal.**
**The main repository's flow is: community recommends -> I code. I am not special, just a regular guy who really loves to code and happens to have all the world's time to code for Iris. No, I'm not a rich guy, I had some money from my previous job but over time as you can imagine, they are spending on my body's requirements, hopefuly we have nice guys and women who cares about these things and making donations that helping me to survive, as I left from my job months ago in order to start coding for Iris explicitly.**.
From the beggining, the project, gained incredible popularity in short-time,
the first month we broke all the rules, we were first on global-github-languages trending list.
**I always say 'we' because you're helping a lot, more than you can even imagine, with your bug reports and feature requests.**
So don't try to fool our selves, Iris has unique features and it's really one of the fastest web frameworks in globe, but these alone are nothing.
So why people loves and trust this framework for a long time of period? Because
**they know where to find the guy who coded this and can ask anything, they get an answer instantly, they know where to stand if something goes bad to their app.**
BUT I'm choosing to give a second chance to people who want to contribution by code. If this time, the people who want to contribute by code do their jobs and mantain their responsibility to iris' users, there are good chances on the future that I will choose some of them, based on the code quality that will be pushed and their activity, in order to ask them for collaboration on the main iris repo too.
So the **[iris-contrib](https://github.com/iris-contrib) organisation and all its repositories will stay open for PRs.** Hopefully future-authors will be more responsible after this long text I wrote here, let's see how things will go on. **Only one term to future authors**: from now and on I want you to be responsible and answer to the future users' issues that are relative to your code. PR's authors must provide adequate support.
**Users is the most important part, we, as software authors, have to respect them. I don't accept any form of contempt to them(users) by anyone.**

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
.settings
.project
2M_donation_timeline.cpd
performance_tips.txt

View File

@ -54,6 +54,8 @@ I'm grateful for all the generous donations. Iris is fully funded by these dona
- [Thanos V.](http://mykonosbiennale.com/) donated 20 EUR at Jenuary 16 of 2017
- [George Opritescu](https://github.com/International) donated 20 EUR at February 7 of 2017
> * The name or/and github username link added after donator's approvement via e-mail.
#### Report, so far
@ -62,4 +64,4 @@ I'm grateful for all the generous donations. Iris is fully funded by these dona
- **Thanks** to all of **You**, 424 EUR donated to [Holy Metropolis of Ioannina](http://www.imioanninon.gr/main/) for clothes, foods and medications for our fellow human beings.
**Available**: VAT(50) + VAT(20) + VAT(20) + VAT(50) + VAT(6) + VAT(20) + VAT(100) + VAT(20) + VAT(20) + VAT(50) + VAT(30) + VAT(50) + VAT(20) + VAT(5) + VAT(25) + VAT(20) - 13 - 424 = 47,45 + 18,97 + 18,61 + 47,05 + 5,34 + 18,97 + 98,04 + 18,97 + 18,61 + 47,95 + 28,24 + 47,05 + 18,97 + 4,59 + 23,80 + 18,77 - 13 - 424 = **43,49 EUR**
**Available**: VAT(50) + VAT(20) + VAT(20) + VAT(50) + VAT(6) + VAT(20) + VAT(100) + VAT(20) + VAT(20) + VAT(50) + VAT(30) + VAT(50) + VAT(20) + VAT(5) + VAT(25) + VAT(20) + VAT(20) - 13 - 424 = 47,45 + 18,97 + 18,61 + 47,05 + 5,34 + 18,97 + 98,04 + 18,97 + 18,61 + 47,95 + 28,24 + 47,05 + 18,97 + 4,59 + 23,80 + 18,77 + 18,97 - 13 - 424 = **62,46 EUR**

1002
HISTORY.md

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@
<br/>
<a href="https://travis-ci.org/kataras/iris"><img src="https://img.shields.io/travis/kataras/iris.svg?style=flat-square" alt="Build Status"></a>
<a href="https://travis-ci.org/kataras/iris"><img src="https://api.travis-ci.org/kataras/iris.svg?branch=v6&style=flat-square" alt="Build Status"></a>
<a href="http://goreportcard.com/report/kataras/iris"><img src="https://img.shields.io/badge/report%20card%20-a%2B-F44336.svg?style=flat-square" alt="http://goreportcard.com/report/kataras/iris"></a>
@ -21,7 +21,7 @@
<br/>
<a href="https://github.com/kataras/iris/blob/master/HISTORY.md"><img src="https://img.shields.io/badge/%20version%20-%206.1.4%20-blue.svg?style=flat-square" alt="CHANGELOG/HISTORY"></a>
<a href="https://github.com/kataras/iris/blob/v6/HISTORY.md"><img src="https://img.shields.io/badge/%20version%20-%206.2.0%20-blue.svg?style=flat-square" alt="CHANGELOG/HISTORY"></a>
<a href="https://github.com/iris-contrib/examples"><img src="https://img.shields.io/badge/%20examples-repository-3362c2.svg?style=flat-square" alt="Examples"></a>
@ -30,9 +30,11 @@
<a href="https://kataras.rocket.chat/channel/iris"><img src="https://img.shields.io/badge/%20community-chat-00BCD4.svg?style=flat-square" alt="Chat"></a><br/>
<br/>
<b>Iris</b> is the fastest HTTP/2 web framework written in Go.
<br/>
<b>Easy</b> to <a href="https://github.com/kataras/iris/tree/master/docs">learn</a> while it's highly customizable,
Iris provides efficient and well-designed toolbox with robust set of features to<br/> <b>create your own
perfect high-performance web application</b> <br/>with unlimited portability using the Go Programming Language.
<br/><br/>
<b>Easy</b> to <a href="https://github.com/kataras/iris/tree/v6/docs">learn</a> while it's highly customizable,
ideally suited for <br/> both experienced and novice developers.<br/><br/>
If you're coming from <a href="https://nodejs.org/en/">Node.js</a> world, this is the <a href="https://github.com/expressjs/express">expressjs</a> for the <a href="https://golang.org">Go Programming Language.</a>
@ -58,11 +60,9 @@ Installation
The only requirement is the [Go Programming Language](https://golang.org/dl/), at least v1.7.
```bash
$ go get -u github.com/kataras/iris/iris
$ go get gopkg.in/kataras/iris.v6
```
![Benchmark Wizzard July 21, 2016- Processing Time Horizontal Graph](https://raw.githubusercontent.com/smallnest/go-web-framework-benchmark/4db507a22c964c9bc9774c5b31afdc199a0fe8b7/benchmark.png)
Overview
-----------
@ -71,26 +71,29 @@ Overview
package main
import (
"github.com/kataras/go-template/html"
"github.com/kataras/iris"
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/view"
)
func main() {
app := iris.New()
// 6 template engines are supported out-of-the-box:
app.Adapt(iris.Devlogger()) // adapt a logger which prints all errors to the os.Stdout
app.Adapt(httprouter.New()) // adapt the adaptors/httprouter or adaptors/gorillamux
// 5 template engines are supported out-of-the-box:
//
// - standard html/template
// - amber
// - django
// - handlebars
// - pug(jade)
// - markdown
//
// Use the html standard engine for all files inside "./views" folder with extension ".html"
// Defaults to:
app.UseTemplate(html.New()).Directory("./views", ".html")
templates := view.HTML("./views", ".html")
app.Adapt(templates)
// http://localhost:6111
// http://localhost:6200
// Method: "GET"
// Render ./views/index.html
app.Get("/", func(ctx *iris.Context) {
@ -102,24 +105,24 @@ func main() {
Layout("layouts/userLayout.html")
{
// Fire userNotFoundHandler when Not Found
// inside http://localhost:6111/users/*anything
// inside http://localhost:6200/users/*anything
userAPI.OnError(404, userNotFoundHandler)
// http://localhost:6111/users
// http://localhost:6200/users
// Method: "GET"
userAPI.Get("/", getAllHandler)
// http://localhost:6111/users/42
// http://localhost:6200/users/42
// Method: "GET"
userAPI.Get("/:id", getByIDHandler)
// http://localhost:6111/users
// http://localhost:6200/users
// Method: "POST"
userAPI.Post("/", saveUserHandler)
}
// Start the server at 0.0.0.0:6111
app.Listen(":6111")
// Start the server at 127.0.0.1:6200
app.Listen(":6200")
}
func getByIDHandler(ctx *iris.Context) {
@ -141,7 +144,7 @@ func getByIDHandler(ctx *iris.Context) {
```
> TIP: Execute `iris run main.go` to enable hot-reload on .go source code changes.
> TIP: Set `app.Config.IsDevelopment = true` to monitor the template changes.
> TIP: Add `templates.Reload(true)` to monitor the template changes.
Documentation
-----------
@ -149,11 +152,13 @@ Documentation
<a href="https://www.gitbook.com/book/kataras/iris/details"><img align="right" width="125" src="https://raw.githubusercontent.com/iris-contrib/website/gh-pages/assets/book/cover_4.jpg"></a>
- The most important is to read [the practical guide](https://docs.iris-go.com/).
- The most important is to read [the practical guide](https://docs.iris-go.com/)
- Navigate through [examples](https://github.com/iris-contrib/examples).
- Read [godocs](https://godoc.org/github.com/kataras/iris) for the details
- [HISTORY.md](https://github.com//kataras/iris/tree/master/HISTORY.md) file is your best friend.
- Navigate through [examples](https://github.com/iris-contrib/examples)
- [HISTORY.md](https://github.com//kataras/iris/tree/v6/HISTORY.md) file is your best friend.
Testing
@ -162,8 +167,8 @@ Testing
You can find RESTFUL test examples by navigating to the following links:
- [gavv/_examples/iris_test.go](https://github.com/gavv/httpexpect/blob/master/_examples/iris_test.go).
- [./http_test.go](https://github.com/kataras/iris/blob/master/http_test.go).
- [./context_test.go](https://github.com/kataras/iris/blob/master/context_test.go).
- [./http_test.go](https://github.com/kataras/iris/blob/v6/http_test.go).
- [./context_test.go](https://github.com/kataras/iris/blob/v6/context_test.go).
FAQ
@ -218,7 +223,7 @@ Besides the fact that we have a [community chat][Chat] for questions or reports
Versioning
------------
Current: **v6.1.4**
Current: **v6.2.0**
v5: https://github.com/kataras/iris/tree/5.0.0
@ -229,4 +234,7 @@ License
Unless otherwise noted, the source files are distributed
under the MIT License found in the [LICENSE file](LICENSE).
Note that some optional components that you may use with Iris requires
different license agreements.
[Chat]: https://kataras.rocket.chat/channel/iris

19
adaptors/cors/LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2014 Olivier Poitrey <rs@dailymotion.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

33
adaptors/cors/cors.go Normal file
View File

@ -0,0 +1,33 @@
package cors
// +------------------------------------------------------------+
// | Cors wrapper usage |
// +------------------------------------------------------------+
//
// import "github.com/kataras/iris/adaptors/cors"
//
// app := iris.New()
// app.Adapt(cors.New(cors.Options{})))
import (
"github.com/rs/cors"
"gopkg.in/kataras/iris.v6"
)
// Options is a configuration container to setup the CORS.
type Options cors.Options
// New returns a new cors router wrapper policy
// with the provided options.
//
// create a new cors middleware, pass its options (casted)
// and pass the .ServeHTTP(w,r,next(http.HandlerFunc)) as the wrapper
// for the whole iris' Router
//
// Unlike the cors middleware, this wrapper wraps the entirely router,
// it cannot be registered to specific routes. It works better and all Options are working.
func New(opts Options) iris.RouterWrapperPolicy { return cors.New(cors.Options(opts)).ServeHTTP }
// Default returns a New cors wrapper with the default options
// accepting GET and POST requests and allowing all origins.
func Default() iris.RouterWrapperPolicy { return New(Options{}) }

View File

@ -0,0 +1,28 @@
Copyright (c) 2012 Rodrigo Moraes author of gorillamux.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,103 @@
## Package information
Gorillamux is a plugin for Iris which overrides the Iris' default router with the [Gorilla Mux](https://github.com/gorilla/mux)
which enables path matching using custom `regexp` ( thing that the Iris' default router doesn't supports for performance reasons).
All these without need to change any of your existing Iris code. All features are supported.
## Install
```sh
$ go get -u github.com/iris-contrib/plugin/gorillamux
```
```go
iris.Plugins.Add(gorillamux.New())
```
## [Example](https://github.com/iris-contrib/examples/tree/master/plugin_gorillamux)
```go
package main
import (
"github.com/iris-contrib/plugin/gorillamux"
"github.com/kataras/iris"
)
func main() {
iris.Plugins.Add(gorillamux.New())
// CUSTOM HTTP ERRORS ARE SUPPORTED
// NOTE: Gorilla mux allows customization only on StatusNotFound(404)
// Iris allows for everything, so you can register any other custom http error
// but you have to call it manually from ctx.EmitError(status_code) // 500 for example
// this will work because it's StatusNotFound:
iris.Default.OnError(iris.StatusNotFound, func(ctx *iris.Context) {
ctx.HTML(iris.StatusNotFound, "<h1> CUSTOM NOT FOUND ERROR PAGE </h1>")
})
// GLOBAL/PARTY MIDDLEWARE ARE SUPPORTED
iris.Default.UseFunc(func(ctx *iris.Context) {
println("Request: " + ctx.Path())
ctx.Next()
})
// http://mydomain.com
iris.Default.Get("/", func(ctx *iris.Context) {
ctx.Writef("Hello from index")
})
/// -------------------------------------- IMPORTANT --------------------------------------
/// GORILLA MUX PARAMETERS(regexp) ARE SUPPORTED
/// http://mydomain.com/api/users/42
/// ---------------------------------------------------------------------------------------
iris.Default.Get("/api/users/{userid:[0-9]+}", func(ctx *iris.Context) {
ctx.Writef("User with id: %s", ctx.Param("userid"))
})
// PER-ROUTE MIDDLEWARE ARE SUPPORTED
// http://mydomain.com/other
iris.Default.Get("/other", func(ctx *iris.Context) {
ctx.Writef("/other 1 middleware \n")
ctx.Next()
}, func(ctx *iris.Context) {
ctx.HTML(iris.StatusOK, "<b>Hello from /other</b>")
})
// SUBDOMAINS ARE SUPPORTED
// http://admin.mydomain.com
iris.Default.Party("admin.").Get("/", func(ctx *iris.Context) {
ctx.Writef("Hello from admin. subdomain!")
})
// WILDCARD SUBDOMAINS ARE SUPPORTED
// http://api.mydomain.com/hi
// http://admin.mydomain.com/hi
// http://x.mydomain.com/hi
// [depends on your host configuration,
// you will see an example(win) outside of this folder].
iris.Default.Party("*.").Get("/hi", func(ctx *iris.Context) {
ctx.Writef("Hello from wildcard subdomain: %s", ctx.Subdomain())
})
// DOMAIN NAMING IS SUPPORTED
iris.Default.Listen("mydomain.com")
// iris.Default.Listen(":80")
}
/* HOSTS FILE LINES TO RUN THIS EXAMPLE:
127.0.0.1 mydomain.com
127.0.0.1 admin.mydomain.com
127.0.0.1 api.mydomain.com
127.0.0.1 x.mydomain.com
*/
```
> Custom domain is totally optionally, you can still use `iris.Default.Listen(":8080")` of course.

View File

@ -0,0 +1,150 @@
package gorillamux
// +------------------------------------------------------------+
// | Usage |
// +------------------------------------------------------------+
//
//
// package main
//
// import (
// "gopkg.in/kataras/iris.v6/adaptors/gorillamux"
// "gopkg.in/kataras/iris.v6"
// )
//
// func main() {
// app := iris.New()
//
// app.Adapt(gorillamux.New()) // Add this line and you're ready.
//
// app.Get("/api/users/{userid:[0-9]+}", func(ctx *iris.Context) {
// ctx.Writef("User with id: %s", ctx.Param("userid"))
// })
//
// app.Listen(":8080")
// }
import (
"net/http"
"strings"
"github.com/gorilla/mux"
"gopkg.in/kataras/iris.v6"
)
const dynamicSymbol = '{'
// New returns a new gorilla mux router which can be plugged inside iris.
// This is magic.
func New() iris.Policies {
router := mux.NewRouter()
var logger func(iris.LogMode, string)
return iris.Policies{
EventPolicy: iris.EventPolicy{Boot: func(s *iris.Framework) {
logger = s.Log
}},
RouterReversionPolicy: iris.RouterReversionPolicy{
// path normalization done on iris' side
StaticPath: func(path string) string {
i := strings.IndexByte(path, dynamicSymbol)
if i > -1 {
return path[0:i]
}
return path
},
WildcardPath: func(requestPath string, paramName string) string {
return requestPath + "/{" + paramName + ":.*}"
},
// Note: on gorilla mux the {{ url }} and {{ path}} should give the key and the value, not only the values by order.
// {{ url "nameOfTheRoute" "parameterName" "parameterValue"}}.
//
// so: {{ url "providerLink" "facebook"}} should become
// {{ url "providerLink" "provider" "facebook"}}
// for a path: "/auth/{provider}" with name 'providerLink'
URLPath: func(r iris.RouteInfo, args ...string) string {
if r == nil {
return ""
}
if gr := router.Get(r.Name()); gr != nil {
u, err := gr.URLPath(args...)
if err != nil {
logger(iris.DevMode, "error on gorilla mux adaptor's URLPath(reverse routing): "+err.Error())
return ""
}
return u.Path
}
return ""
},
RouteContextLinker: func(r iris.RouteInfo, ctx *iris.Context) {
if r == nil {
return
}
route := router.Get(r.Name())
if route != nil {
mapToContext(ctx.Request, r.Middleware(), ctx)
}
},
},
RouterBuilderPolicy: func(repo iris.RouteRepository, context iris.ContextPool) http.Handler {
repo.Visit(func(route iris.RouteInfo) {
registerRoute(route, router, context)
})
router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.Acquire(w, r)
// to catch custom 404 not found http errors may registered by user
ctx.EmitError(iris.StatusNotFound)
context.Release(ctx)
})
return router
},
}
}
func mapToContext(r *http.Request, middleware iris.Middleware, ctx *iris.Context) {
if params := mux.Vars(r); len(params) > 0 {
// set them with ctx.Set in order to be accesible by ctx.Param in the user's handler
for k, v := range params {
ctx.Set(k, v)
}
}
// including the iris.Default.Use/UseFunc and the route's middleware,
// main handler and any done handlers.
ctx.Middleware = middleware
}
// so easy:
func registerRoute(route iris.RouteInfo, gorillaRouter *mux.Router, context iris.ContextPool) {
if route.IsOnline() {
handler := func(w http.ResponseWriter, r *http.Request) {
ctx := context.Acquire(w, r)
mapToContext(r, route.Middleware(), ctx)
ctx.Do()
context.Release(ctx)
}
// remember, we get a new iris.Route foreach of the HTTP Methods, so this should be work
methods := []string{route.Method()}
// if route has cors then we register the route with the "OPTIONS" method too
if route.HasCors() {
methods = append(methods, http.MethodOptions)
}
gorillaRoute := gorillaRouter.HandleFunc(route.Path(), handler).Methods(methods...).Name(route.Name())
subdomain := route.Subdomain()
if subdomain != "" {
if subdomain == "*." {
// it's an iris wildcard subdomain
// so register it as wildcard on gorilla mux too (hopefuly, it supports these things)
subdomain = "{subdomain}."
} else {
// it's a static subdomain (which contains the dot)
}
// host = subdomain + listening host
gorillaRoute.Host(subdomain + context.Framework().Config.VHost)
}
}
}

View File

@ -0,0 +1,47 @@
The MIT License (MIT)
Copyright (c) 2016-2017 Gerasimos Maropoulos
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Copyright (c) 2013 Julien Schmidt. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The names of the contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL JULIEN SCHMIDT BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,39 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
)
func hello(ctx *iris.Context) {
ctx.Writef("Hello from %s", ctx.Path())
}
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New())
app.OnError(iris.StatusNotFound, func(ctx *iris.Context) {
ctx.HTML(iris.StatusNotFound, "<h1>Custom not found handler </h1>")
})
app.Get("/", hello)
app.Get("/users/:userid", func(ctx *iris.Context) {
ctx.Writef("Hello user with id: %s", ctx.Param("userid"))
})
app.Get("/myfiles/*file", func(ctx *iris.Context) {
ctx.HTML(iris.StatusOK, "Hello, the dynamic path after /myfiles is:<br/> <b>"+ctx.Param("file")+"</b>")
})
app.Get("/users/:userid/messages/:messageid", func(ctx *iris.Context) {
ctx.HTML(iris.StatusOK, `Message from user with id:<br/> <b>`+ctx.Param("userid")+`</b>,
message id: <b>`+ctx.Param("messageid")+`</b>`)
})
// http://127.0.0.1:8080/users/42
// http://127.0.0.1:8080/myfiles/mydirectory/myfile.zip
// http://127.0.0.1:8080/users/42/messages/1
app.Listen(":8080")
}

View File

@ -0,0 +1,725 @@
package httprouter
// +------------------------------------------------------------+
// | Usage |
// +------------------------------------------------------------+
//
//
// package main
//
// import (
// "gopkg.in/kataras/iris.v6/adaptors/httprouter"
// "gopkg.in/kataras/iris.v6"
// )
//
// func main() {
// app := iris.New()
//
// app.Adapt(httprouter.New()) // Add this line and you're ready.
//
// app.Get("/api/users/:userid", func(ctx *iris.Context) {
// ctx.Writef("User with id: %s", ctx.Param("userid"))
// })
//
// app.Listen(":8080")
// }
import (
"net/http"
"strings"
"github.com/kataras/go-errors"
"gopkg.in/kataras/iris.v6"
)
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('*')
isRoot entryCase = iota
hasParams
matchEverything
)
type (
// entryCase is the type which the type of muxEntryusing in order to determinate what type (parameterized, anything, static...) is the perticular node
entryCase uint8
// muxEntry is the node of a tree of the routes,
// in order to learn how this is working, google 'trie' or watch this lecture: https://www.youtube.com/watch?v=uhAUk63tLRM
// this method is used by the BSD's kernel also
muxEntry struct {
part string
entryCase entryCase
hasWildNode bool
tokens string
nodes []*muxEntry
middleware iris.Middleware
precedence uint64
paramsLen uint8
}
)
var (
errMuxEntryConflictsWildcard = errors.New(`
Router: '%s' in new path '%s'
conflicts with existing wildcarded route with path: '%s'
in existing prefix of'%s' `)
errMuxEntryMiddlewareAlreadyExists = errors.New(`
Router: Middleware were already registered for the path: '%s'`)
errMuxEntryInvalidWildcard = errors.New(`
Router: More than one wildcard found in the path part: '%s' in route's path: '%s'`)
errMuxEntryConflictsExistingWildcard = errors.New(`
Router: Wildcard for route path: '%s' conflicts with existing children in route path: '%s'`)
errMuxEntryWildcardUnnamed = errors.New(`
Router: Unnamed wildcard found in path: '%s'`)
errMuxEntryWildcardInvalidPlace = errors.New(`
Router: Wildcard is only allowed at the end of the path, in the route path: '%s'`)
errMuxEntryWildcardConflictsMiddleware = errors.New(`
Router: Wildcard conflicts with existing middleware for the route path: '%s'`)
errMuxEntryWildcardMissingSlash = errors.New(`
Router: No slash(/) were found before wildcard in the route path: '%s'`)
)
// 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)
}
// findLower returns the smaller number between a and b
func findLower(a, b int) int {
if a <= b {
return a
}
return b
}
// add adds a muxEntry to the existing muxEntry or to the tree if no muxEntry has the prefix of
func (e *muxEntry) add(path string, middleware iris.Middleware) error {
fullPath := path
e.precedence++
numParams := getParamsLen(path)
if len(e.part) > 0 || len(e.nodes) > 0 {
loop:
for {
if numParams > e.paramsLen {
e.paramsLen = numParams
}
i := 0
max := findLower(len(path), len(e.part))
for i < max && path[i] == e.part[i] {
i++
}
if i < len(e.part) {
node := muxEntry{
part: e.part[i:],
hasWildNode: e.hasWildNode,
tokens: e.tokens,
nodes: e.nodes,
middleware: e.middleware,
precedence: e.precedence - 1,
}
for i := range node.nodes {
if node.nodes[i].paramsLen > node.paramsLen {
node.paramsLen = node.nodes[i].paramsLen
}
}
e.nodes = []*muxEntry{&node}
e.tokens = string([]byte{e.part[i]})
e.part = path[:i]
e.middleware = nil
e.hasWildNode = false
}
if i < len(path) {
path = path[i:]
if e.hasWildNode {
e = e.nodes[0]
e.precedence++
if numParams > e.paramsLen {
e.paramsLen = numParams
}
numParams--
if len(path) >= len(e.part) && e.part == path[:len(e.part)] &&
// Check for longer wildcard, e.g. :name and :names
(len(e.part) >= len(path) || path[len(e.part)] == '/') {
continue loop
} else {
// Wildcard conflict
part := strings.SplitN(path, "/", 2)[0]
prefix := fullPath[:strings.Index(fullPath, part)] + e.part
return errMuxEntryConflictsWildcard.Format(fullPath, e.part, prefix)
}
}
c := path[0]
if e.entryCase == hasParams && c == slashByte && len(e.nodes) == 1 {
e = e.nodes[0]
e.precedence++
continue loop
}
for i := range e.tokens {
if c == e.tokens[i] {
i = e.precedenceTo(i)
e = e.nodes[i]
continue loop
}
}
if c != parameterStartByte && c != matchEverythingByte {
e.tokens += string([]byte{c})
node := &muxEntry{
paramsLen: numParams,
}
e.nodes = append(e.nodes, node)
e.precedenceTo(len(e.tokens) - 1)
e = node
}
return e.addNode(numParams, path, fullPath, middleware)
} else if i == len(path) {
if e.middleware != nil {
return errMuxEntryMiddlewareAlreadyExists.Format(fullPath)
}
e.middleware = middleware
}
return nil
}
} else {
if err := e.addNode(numParams, path, fullPath, middleware); err != nil {
return err
}
e.entryCase = isRoot
}
return nil
}
// addNode adds a muxEntry as children to other muxEntry
func (e *muxEntry) addNode(numParams uint8, path string, fullPath string, middleware iris.Middleware) error {
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] != slashByte {
switch path[end] {
case parameterStartByte, matchEverythingByte:
return errMuxEntryInvalidWildcard.Format(path[i:], fullPath)
default:
end++
}
}
if len(e.nodes) > 0 {
return errMuxEntryConflictsExistingWildcard.Format(path[i:end], fullPath)
}
if end-i < 2 {
return errMuxEntryWildcardUnnamed.Format(fullPath)
}
if c == parameterStartByte {
if i > 0 {
e.part = path[offset:i]
offset = i
}
child := &muxEntry{
entryCase: hasParams,
paramsLen: numParams,
}
e.nodes = []*muxEntry{child}
e.hasWildNode = true
e = child
e.precedence++
numParams--
if end < max {
e.part = path[offset:end]
offset = end
child := &muxEntry{
paramsLen: numParams,
precedence: 1,
}
e.nodes = []*muxEntry{child}
e = child
}
} else {
if end != max || numParams > 1 {
return errMuxEntryWildcardInvalidPlace.Format(fullPath)
}
if len(e.part) > 0 && e.part[len(e.part)-1] == '/' {
return errMuxEntryWildcardConflictsMiddleware.Format(fullPath)
}
i--
if path[i] != slashByte {
return errMuxEntryWildcardMissingSlash.Format(fullPath)
}
e.part = path[offset:i]
child := &muxEntry{
hasWildNode: true,
entryCase: matchEverything,
paramsLen: 1,
}
e.nodes = []*muxEntry{child}
e.tokens = string(path[i])
e = child
e.precedence++
child = &muxEntry{
part: path[i:],
entryCase: matchEverything,
paramsLen: 1,
middleware: middleware,
precedence: 1,
}
e.nodes = []*muxEntry{child}
return nil
}
}
e.part = path[offset:]
e.middleware = middleware
return nil
}
// get is used by the Router, it finds and returns the correct muxEntry for a path
func (e *muxEntry) get(path string, ctx *iris.Context) (mustRedirect bool) {
loop:
for {
if len(path) > len(e.part) {
if path[:len(e.part)] == e.part {
path = path[len(e.part):]
if !e.hasWildNode {
c := path[0]
for i := range e.tokens {
if c == e.tokens[i] {
e = e.nodes[i]
continue loop
}
}
mustRedirect = (path == slash && e.middleware != nil)
return
}
e = e.nodes[0]
switch e.entryCase {
case hasParams:
end := 0
for end < len(path) && path[end] != '/' {
end++
}
ctx.Set(e.part[1:], path[:end])
if end < len(path) {
if len(e.nodes) > 0 {
path = path[end:]
e = e.nodes[0]
continue loop
}
mustRedirect = (len(path) == end+1)
return
}
if ctx.Middleware = e.middleware; ctx.Middleware != nil {
return
} else if len(e.nodes) == 1 {
e = e.nodes[0]
mustRedirect = (e.part == slash && e.middleware != nil)
}
return
case matchEverything:
ctx.Set(e.part[2:], path)
ctx.Middleware = e.middleware
return
default:
return
}
}
} else if path == e.part {
if ctx.Middleware = e.middleware; ctx.Middleware != nil {
return
}
if path == slash && e.hasWildNode && e.entryCase != isRoot {
mustRedirect = true
return
}
for i := range e.tokens {
if e.tokens[i] == slashByte {
e = e.nodes[i]
mustRedirect = (len(e.part) == 1 && e.middleware != nil) ||
(e.entryCase == matchEverything && e.nodes[0].middleware != nil)
return
}
}
return
}
mustRedirect = (path == slash) ||
(len(e.part) == len(path)+1 && e.part[len(path)] == slashByte &&
path == e.part[:len(e.part)-1] && e.middleware != nil)
return
}
}
// precedenceTo just adds the priority of this muxEntry by an index
func (e *muxEntry) precedenceTo(index int) int {
e.nodes[index].precedence++
_precedence := e.nodes[index].precedence
newindex := index
for newindex > 0 && e.nodes[newindex-1].precedence < _precedence {
tmpN := e.nodes[newindex-1]
e.nodes[newindex-1] = e.nodes[newindex]
e.nodes[newindex] = tmpN
newindex--
}
if newindex != index {
e.tokens = e.tokens[:newindex] +
e.tokens[index:index+1] +
e.tokens[newindex:index] + e.tokens[index+1:]
}
return newindex
}
type (
muxTree struct {
method string
// subdomain is empty for default-hostname routes,
// ex: mysubdomain.
subdomain string
entry *muxEntry
}
serveMux struct {
garden []*muxTree
maxParameters uint8
methodEqual func(string, string) bool
hosts bool
}
)
// New returns a new iris' policy to create and attach the router.
// It's based on the julienschmidt/httprouter with more features and some iris-relative performance tips:
// subdomains(wildcard/dynamic and static) and faster parameters set (use of the already-created context's values)
// and support for reverse routing.
func New() iris.Policies {
var logger func(iris.LogMode, string)
mux := &serveMux{
methodEqual: func(reqMethod string, treeMethod string) bool {
return reqMethod == treeMethod
},
}
matchEverythingString := string(matchEverythingByte)
return iris.Policies{
EventPolicy: iris.EventPolicy{
Boot: func(s *iris.Framework) {
logger = s.Log
},
},
RouterReversionPolicy: iris.RouterReversionPolicy{
// path normalization done on iris' side
StaticPath: func(path string) string {
i := strings.IndexByte(path, parameterStartByte)
x := strings.IndexByte(path, matchEverythingByte)
if i > -1 {
return path[0:i]
}
if x > -1 {
return path[0:x]
}
return path
},
WildcardPath: func(path string, paramName string) string {
return path + slash + matchEverythingString + paramName
},
// URLPath: func(r iris.RouteInfo, args ...string) string {
// argsLen := len(args)
//
// // we have named parameters but arguments not given
// if argsLen == 0 && r.formattedParts > 0 {
// return ""
// } else if argsLen == 0 && r.formattedParts == 0 {
// // it's static then just return the path
// return r.path
// }
//
// // we have arguments but they are much more than the named parameters
//
// // 1 check if we have /*, if yes then join all arguments to one as path and pass that as parameter
// if argsLen > r.formattedParts {
// if r.path[len(r.path)-1] == matchEverythingByte {
// // we have to convert each argument to a string in this case
//
// argsString := make([]string, argsLen, argsLen)
//
// for i, v := range args {
// if s, ok := v.(string); ok {
// argsString[i] = s
// } else if num, ok := v.(int); ok {
// argsString[i] = strconv.Itoa(num)
// } else if b, ok := v.(bool); ok {
// argsString[i] = strconv.FormatBool(b)
// } else if arr, ok := v.([]string); ok {
// if len(arr) > 0 {
// argsString[i] = arr[0]
// argsString = append(argsString, arr[1:]...)
// }
// }
// }
//
// parameter := strings.Join(argsString, slash)
// result := fmt.Sprintf(r.formattedPath, parameter)
// return result
// }
// // 2 if !1 return false
// return ""
// }
//
// arguments := joinPathArguments(args...)
//
// return fmt.Sprintf(r.formattedPath, arguments...)
// },
RouteContextLinker: func(r iris.RouteInfo, ctx *iris.Context) {
tree := mux.getTree(r.Method(), r.Subdomain())
if tree != nil {
tree.entry.get(ctx.Request.URL.Path, ctx)
}
},
},
RouterBuilderPolicy: func(repo iris.RouteRepository, context iris.ContextPool) http.Handler {
fatalErr := false
repo.Visit(func(r iris.RouteInfo) {
if fatalErr {
return
}
// add to the registry tree
method := r.Method()
subdomain := r.Subdomain()
path := r.Path()
middleware := r.Middleware()
tree := mux.getTree(method, subdomain)
if tree == nil {
//first time we register a route to this method with this domain
tree = &muxTree{method: method, subdomain: subdomain, entry: &muxEntry{}}
mux.garden = append(mux.garden, tree)
}
// I decide that it's better to explicit give subdomain and a path to it than registeredPath(mysubdomain./something) now its: subdomain: mysubdomain., path: /something
// we have different tree for each of subdomains, now you can use everything you can use with the normal paths ( before you couldn't set /any/*path)
if err := tree.entry.add(path, middleware); err != nil {
// while ProdMode means that the iris should not continue running
// by-default it panics on these errors, but to make sure let's introduce the fatalErr to stop visiting
fatalErr = true
logger(iris.ProdMode, "fatal error on httprouter build adaptor: "+err.Error())
return
}
if mp := tree.entry.paramsLen; mp > mux.maxParameters {
mux.maxParameters = mp
}
// check for method equality if at least one route has cors
if r.HasCors() {
mux.methodEqual = func(reqMethod string, treeMethod string) bool {
// preflights
return reqMethod == iris.MethodOptions || reqMethod == treeMethod
}
}
if subdomain != "" {
mux.hosts = true
}
})
if !fatalErr {
return mux.buildHandler(context)
}
return nil
},
}
}
func (mux *serveMux) getTree(method string, subdomain string) *muxTree {
for i := range mux.garden {
t := mux.garden[i]
if t.method == method && t.subdomain == subdomain {
return t
}
}
return nil
}
func (mux *serveMux) buildHandler(pool iris.ContextPool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pool.Run(w, r, func(context *iris.Context) {
routePath := context.Path()
for i := range mux.garden {
tree := mux.garden[i]
if !mux.methodEqual(context.Request.Method, tree.method) {
continue
}
if mux.hosts && tree.subdomain != "" {
// context.VirtualHost() is a slow method because it makes
// string.Replaces but user can understand that if subdomain then server will have some nano/or/milleseconds performance cost
requestHost := context.VirtualHostname()
hostname := context.Framework().Config.VHost
if requestHost != hostname {
//println(requestHost + " != " + mux.hostname)
// we have a subdomain
if strings.Contains(tree.subdomain, iris.DynamicSubdomainIndicator) {
} else {
//println(requestHost + " = " + mux.hostname)
// mux.host = iris-go.com:8080, the subdomain for example is api.,
// so the host must be api.iris-go.com:8080
if tree.subdomain+hostname != requestHost {
// go to the next tree, we have a subdomain but it is not the correct
continue
}
}
} else {
//("it's subdomain but the request is the same as the listening addr mux.host == requestHost =>" + mux.host + "=" + requestHost + " ____ and tree's subdomain was: " + tree.subdomain)
continue
}
}
mustRedirect := tree.entry.get(routePath, context) // pass the parameters here for 0 allocation
if context.Middleware != nil {
// ok we found the correct route, serve it and exit entirely from here
//ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent)
context.Do()
return
} else if mustRedirect && !context.Framework().Config.DisablePathCorrection { // && context.Method() == MethodConnect {
reqPath := routePath
pathLen := len(reqPath)
if pathLen > 1 {
if reqPath[pathLen-1] == '/' {
reqPath = reqPath[:pathLen-1] //remove the last /
} else {
//it has path prefix, it doesn't ends with / and it hasn't be found, then just add the slash
reqPath = reqPath + "/"
}
urlToRedirect := reqPath
statusForRedirect := iris.StatusMovedPermanently // StatusMovedPermanently, this document is obselte, clients caches this.
if tree.method == iris.MethodPost ||
tree.method == iris.MethodPut ||
tree.method == iris.MethodDelete {
statusForRedirect = iris.StatusTemporaryRedirect // To maintain POST data
}
context.Redirect(urlToRedirect, statusForRedirect)
// 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 == iris.MethodGet {
note := "<a href=\"" + HTMLEscape(urlToRedirect) + "\">Moved Permanently</a>.\n"
// ignore error
context.WriteString(note)
}
return
}
}
// not found
break
}
// https://github.com/kataras/iris/issues/469
if context.Framework().Config.FireMethodNotAllowed {
for i := range mux.garden {
tree := mux.garden[i]
if !mux.methodEqual(context.Method(), tree.method) {
continue
}
}
context.EmitError(iris.StatusMethodNotAllowed)
return
}
context.EmitError(iris.StatusNotFound)
})
})
}
//THESE ARE FROM Go Authors "html" package
var htmlReplacer = strings.NewReplacer(
"&", "&amp;",
"<", "&lt;",
">", "&gt;",
// "&#34;" is shorter than "&quot;".
`"`, "&#34;",
// "&#39;" is shorter than "&apos;" and apos was not in HTML until HTML5.
"'", "&#39;",
)
// HTMLEscape returns a string which has no valid html code
func HTMLEscape(s string) string {
return htmlReplacer.Replace(s)
}

View File

@ -0,0 +1,23 @@
package httprouter
func joinPathArguments(args ...interface{}) []interface{} {
arguments := args[0:]
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
}
// replace the current slice
// with the first string element (always as interface{})
arguments[i] = interfaceArr[0]
// append the rest of them to the slice itself
// the range is not affected by these things in go,
// so we are safe to do it.
arguments = append(args, interfaceArr[1:]...)
}
}
}
return arguments
}

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016-2017 Gerasimos Maropoulos
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,89 @@
## Package information
This is an Iris and typescript bridge plugin.
1. Search for typescript files (.ts)
2. Search for typescript projects (.tsconfig)
3. If 1 || 2 continue else stop
4. Check if typescript is installed, if not then auto-install it (always inside npm global modules, -g)
5. If typescript project then build the project using tsc -p $dir
6. If typescript files and no project then build each typescript using tsc $filename
7. Watch typescript files if any changes happens, then re-build (5|6)
> Note: Ignore all typescript files & projects whose path has '/node_modules/'
## Install
```sh
$ go get -u github.com/iris-contrib/plugin/typescript
```
## Options
This plugin has **optionally** options
1. Bin: string, the typescript installation path/bin/tsc or tsc.cmd, if empty then it will search to the global npm modules
2. Dir: string, Dir set the root, where to search for typescript files/project. Default "./"
3. Ignore: string, comma separated ignore typescript files/project from these directories. Default "" (node_modules are always ignored)
4. Tsconfig: &typescript.Tsconfig{}, here you can set all compilerOptions if no tsconfig.json exists inside the 'Dir'
5. Editor: typescript.Editor(), if setted then alm-tools browser-based typescript IDE will be available. Defailt is nil
> Note: if any string in Ignore doesn't start with './' then it will ignore all files which contains this path string.
For example /node_modules/ will ignore all typescript files that are inside at ANY '/node_modules/', that means and the submodules.
## How to use
```go
package main
import (
"github.com/kataras/iris"
"github.com/iris-contrib/plugin/typescript"
)
func main(){
/* Options
Bin -> the typescript installation path/bin/tsc or tsc.cmd, if empty then it will search to the global npm modules
Dir -> where to search for typescript files/project. Default "./"
Ignore -> comma separated ignore typescript files/project from these directories (/node_modules/ are always ignored). Default ""
Tsconfig -> &typescript.Tsconfig{}, here you can set all compilerOptions if no tsconfig.json exists inside the 'Dir'
Editor -> typescript.Editor(), if setted then alm-tools browser-based typescript IDE will be available. Default is nil.
*/
config := typescript.Config {
Dir: "./scripts/src",
Tsconfig: &typescript.Tsconfig{Module: "commonjs", Target: "es5"}, // or typescript.DefaultTsconfig()
}
//if you want to change only certain option(s) but you want default to all others then you have to do this:
config = typescript.DefaultConfig()
//
iris.Plugins.Add(typescript.New(config)) //or with the default options just: typescript.New()
iris.Default.Get("/", func (ctx *iris.Context){})
iris.Default.Listen(":8080")
}
```
## Editor
[alm-tools](http://alm.tools) is a typescript online IDE/Editor, made by [@basarat](https://twitter.com/basarat) one of the top contributors of the [Typescript](http://www.typescriptlang.org).
Iris gives you the opportunity to edit your client-side using the alm-tools editor, via the editor plugin.
With typescript plugin you have to set the Editor option and you're ready:
```go
typescript.Config {
//...
Editor: typescript.Editor("username","passowrd")
//...
}
```
> [Read more](https://github.com/kataras/iris/tree/development/plugin/editor) for Editor

View File

@ -0,0 +1,31 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/typescript"
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New()) // adapt a router, order doesn't matters but before Listen.
ts := typescript.New()
ts.Config.Dir = "./www/scripts"
app.Adapt(ts) // adapt the typescript compiler adaptor
app.StaticWeb("/", "./www") // serve the index.html
app.Listen(":8080")
}
// open http://localhost:8080
// go to ./www/scripts/app.ts
// make a change
// reload the http://localhost:8080 and you should see the changes
//
// what it does?
// - compiles the typescript files using default compiler options if not tsconfig found
// - watches for changes on typescript files, if a change then it recompiles the .ts to .js
//
// same as you used to do with gulp-like tools, but here at Iris I do my bests to help GO developers.

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>Load my script (lawl)</title>
</head>
<body>
<script src="scripts/app.js"></script>
</body>
</html>

View File

@ -0,0 +1,16 @@
class User{
private name: string;
constructor(fullname:string) {
this.name = fullname;
}
Hi(msg: string): string {
return msg + " "+ this.name;
}
}
var user = new User("kataras");
var hi = user.Hi("Hello");
window.alert(hi);

View File

@ -0,0 +1,192 @@
package typescript
import (
"encoding/json"
"io/ioutil"
"os"
"reflect"
"strconv"
"gopkg.in/kataras/iris.v6/adaptors/typescript/npm"
)
var (
pathSeparator = string(os.PathSeparator)
nodeModules = pathSeparator + "node_modules" + pathSeparator
)
type (
// Tsconfig the struct for tsconfig.json
Tsconfig struct {
CompilerOptions CompilerOptions `json:"compilerOptions"`
Exclude []string `json:"exclude"`
}
// CompilerOptions contains all the compiler options used by the tsc (typescript compiler)
CompilerOptions struct {
Declaration bool `json:"declaration"`
Module string `json:"module"`
Target string `json:"target"`
Watch bool `json:"watch"`
Charset string `json:"charset"`
Diagnostics bool `json:"diagnostics"`
EmitBOM bool `json:"emitBOM"`
EmitDecoratorMetadata bool `json:"emitDecoratorMetadata"`
ExperimentalDecorators bool `json:"experimentalDecorators"`
InlineSourceMap bool `json:"inlineSourceMap"`
InlineSources bool `json:"inlineSources"`
IsolatedModules bool `json:"isolatedModules"`
Jsx string `json:"jsx"`
ReactNamespace string `json:"reactNamespace"`
ListFiles bool `json:"listFiles"`
Locale string `json:"locale"`
MapRoot string `json:"mapRoot"`
ModuleResolution string `json:"moduleResolution"`
NewLine string `json:"newLine"`
NoEmit bool `json:"noEmit"`
NoEmitOnError bool `json:"noEmitOnError"`
NoEmitHelpers bool `json:"noEmitHelpers"`
NoImplicitAny bool `json:"noImplicitAny"`
NoLib bool `json:"noLib"`
NoResolve bool `json:"noResolve"`
SkipDefaultLibCheck bool `json:"skipDefaultLibCheck"`
OutDir string `json:"outDir"`
OutFile string `json:"outFile"`
PreserveConstEnums bool `json:"preserveConstEnums"`
Pretty bool `json:"pretty"`
RemoveComments bool `json:"removeComments"`
RootDir string `json:"rootDir"`
SourceMap bool `json:"sourceMap"`
SourceRoot string `json:"sourceRoot"`
StripInternal bool `json:"stripInternal"`
SuppressExcessPropertyErrors bool `json:"suppressExcessPropertyErrors"`
SuppressImplicitAnyIndexErrors bool `json:"suppressImplicitAnyIndexErrors"`
AllowUnusedLabels bool `json:"allowUnusedLabels"`
NoImplicitReturns bool `json:"noImplicitReturns"`
NoFallthroughCasesInSwitch bool `json:"noFallthroughCasesInSwitch"`
AllowUnreachableCode bool `json:"allowUnreachableCode"`
ForceConsistentCasingInFileNames bool `json:"forceConsistentCasingInFileNames"`
AllowSyntheticDefaultImports bool `json:"allowSyntheticDefaultImports"`
AllowJs bool `json:"allowJs"`
NoImplicitUseStrict bool `json:"noImplicitUseStrict"`
}
// Config the configs for the Typescript plugin
// Has five (5) fields
//
// 1. Bin: string, the typescript installation directory/typescript/lib/tsc.js, if empty it will search inside global npm modules
// 2. Dir: string, Dir set the root, where to search for typescript files/project. Default "./"
// 3. Ignore: string, comma separated ignore typescript files/project from these directories. Default "" (node_modules are always ignored)
// 4. Tsconfig: &typescript.Tsconfig{}, here you can set all compilerOptions if no tsconfig.json exists inside the 'Dir'
// 5. Editor: typescript.Editor("username","password"), if setted then alm-tools browser-based typescript IDE will be available. Defailt is nil
Config struct {
// Bin the path of the tsc binary file
// if empty then the plugin tries to find it
Bin string
// Dir the client side directory, which typescript (.ts) files are live
Dir string
// Ignore ignore folders, default is /node_modules/
Ignore string
// Tsconfig the typescript build configs, including the compiler's options
Tsconfig *Tsconfig
}
)
// CompilerArgs returns the CompilerOptions' contents of the Tsconfig
// it reads the json tags, add '--' at the start of each one and returns an array of strings
// this is from file
func (tsconfig *Tsconfig) CompilerArgs() []string {
val := reflect.ValueOf(tsconfig).Elem().FieldByName("CompilerOptions") // -> for tsconfig *Tsconfig
// val := reflect.ValueOf(tsconfig.CompilerOptions)
compilerOpts := make([]string, 0) // 0 because we don't know the real valid options yet.
for i := 0; i < val.NumField(); i++ {
typeField := val.Type().Field(i)
valueFieldG := val.Field(i)
var valueField string
// only if it's string or int we need to put that
if valueFieldG.Kind() == reflect.String {
//if valueFieldG.String() != "" {
//valueField = strconv.QuoteToASCII(valueFieldG.String())
// }
valueField = valueFieldG.String()
} else if valueFieldG.Kind() == reflect.Int {
if valueFieldG.Int() > 0 {
valueField = strconv.Itoa(int(valueFieldG.Int()))
}
} else if valueFieldG.Kind() == reflect.Bool {
valueField = strconv.FormatBool(valueFieldG.Bool())
}
if valueField != "" && valueField != "false" {
// var opt string
// key := typeField.Tag.Get("json")
// // it's bool value of true then just --key, for example --watch
// if valueField == "true" {
// opt = "--" + key
// } else {
// // it's a string now, for example -m commonjs
// opt = "-" + string(key[0]) + " " + valueField
// }
key := "--" + typeField.Tag.Get("json")
compilerOpts = append(compilerOpts, key)
// the form is not '--module ES6' but os.Exec should recognise them as arguments
// so we need to put the values on the next index
if valueField != "true" {
// it's a string now, for example -m commonjs
compilerOpts = append(compilerOpts, valueField)
}
}
}
return compilerOpts
}
// FromFile reads a file & returns the Tsconfig by its contents
func FromFile(tsConfigAbsPath string) (config Tsconfig, err error) {
file, err := ioutil.ReadFile(tsConfigAbsPath)
if err != nil {
return
}
config = Tsconfig{}
err = json.Unmarshal(file, &config)
return
}
// DefaultTsconfig returns the default Tsconfig, with CompilerOptions module: commonjs, target: es5 and ignore the node_modules
func DefaultTsconfig() Tsconfig {
return Tsconfig{
CompilerOptions: CompilerOptions{
Module: "commonjs",
Target: "ES6",
Jsx: "react",
ModuleResolution: "classic",
Locale: "en",
Watch: true,
NoImplicitAny: false,
SourceMap: false,
},
Exclude: []string{"node_modules"},
}
}
// DefaultConfig returns the default Options of the Typescript adaptor
// Bin and Editor are setting in runtime via the adaptor
func DefaultConfig() Config {
root, err := os.Getwd()
if err != nil {
panic("Typescript Adaptor: Cannot get the Current Working Directory !!! [os.getwd()]")
}
compilerTsConfig := DefaultTsconfig()
c := Config{
Dir: root + pathSeparator,
Ignore: nodeModules,
Tsconfig: &compilerTsConfig,
}
c.Bin = npm.NodeModuleAbs("typescript/lib/tsc.js")
return c
}

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Basarat Ali Syed author of alm-tools
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,35 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/typescript" // optinally
"gopkg.in/kataras/iris.v6/adaptors/typescript/editor"
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New()) // adapt a router, order doesn't matters
// optionally but good to have, I didn't put inside editor or the editor in the typescript compiler adaptors
// because you may use tools like gulp and you may use the editor without the typescript compiler adaptor.
// but if you need auto-compilation on .ts, we have a solution:
ts := typescript.New()
ts.Config.Dir = "./www/scripts/"
app.Adapt(ts) // adapt the typescript compiler adaptor
editorConfig := editor.Config{
Hostname: "127.0.0.1",
Port: 4444,
WorkingDir: "./www/scripts/", // "/path/to/the/client/side/directory/",
Username: "myusername",
Password: "mypassword",
}
e := editor.New(editorConfig)
app.Adapt(e) // adapt the editor
app.StaticWeb("/", "./www") // serve the index.html
app.Listen(":8080")
}

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>Load my script (lawl)</title>
</head>
<body>
<script src="scripts/app.js"></script>
</body>
</html>

View File

@ -0,0 +1,16 @@
class User{
private name: string;
constructor(fullname:string) {
this.name = fullname;
}
Hi(msg: string): string {
return msg + " " + this.name;
}
}
var user = new User("kataras");
var hi = user.Hi("Hello");
window.alert(hi);

View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": false,
"removeComments": true,
"preserveConstEnums": true,
"sourceMap": false,
"target": "ES5",
"noEmit": false,
"watch":true,
"noEmitOnError": true,
"experimentalDecorators": false,
"outDir": "./",
"charset": "UTF-8",
"noLib": false,
"diagnostics": true,
"declaration": false
},
"files": [
"./app.ts"
]
}

View File

@ -0,0 +1,78 @@
package editor
import (
"github.com/imdario/mergo"
"os"
)
// Default values for the configuration
const (
DefaultPort = 4444
)
// Config the configs for the Editor plugin
type Config struct {
// Hostname if empty used the iris server's hostname
Hostname string
// Port if 0 4444
Port int
// KeyFile the key file(ssl optional)
KeyFile string
// CertFile the cert file (ssl optional)
CertFile string
// WorkingDir if empty "./"
WorkingDir string
// Username defaults to empty, you should set this
Username string
// Password defaults to empty, you should set this
Password string
// DisableOutput set that to true if you don't care about alm-tools' messages
// they are useful because that the default value is "false"
DisableOutput bool
}
// DefaultConfig returns the default configs for the Editor plugin
func DefaultConfig() Config {
// explicit
return Config{
Hostname: "",
Port: 4444,
KeyFile: "",
CertFile: "",
WorkingDir: "." + string(os.PathSeparator), // alm-tools should end with path separator.
Username: "",
Password: "",
DisableOutput: false,
}
}
// Merge merges the default with the given config and returns the result
func (c Config) Merge(cfg []Config) (config Config) {
if len(cfg) > 0 {
config = cfg[0]
if err := mergo.Merge(&config, c); err != nil {
if !c.DisableOutput {
panic(err)
}
}
} else {
_default := c
config = _default
}
return
}
// MergeSingle merges the default with the given config and returns the result
func (c Config) MergeSingle(cfg Config) (config Config) {
config = cfg
if err := mergo.Merge(&config, c); err != nil {
if !c.DisableOutput {
panic(err)
}
}
return
}

View File

@ -0,0 +1,216 @@
package editor
// +------------------------------------------------------------+
// | Editor usage |
// +------------------------------------------------------------+
//
// import "gopkg.in/kataras/iris.v6/adaptors/editor"
//
// e := editor.New(editor.Config{})
// app.Adapt(e)
//
// app.Listen(":8080")
//
// +------------------------------------------------------------+
// | General notes for authentication |
// +------------------------------------------------------------+
//
// The Authorization specifies the authentication mechanism (in this case Basic) followed by the username and password.
// Although, the string aHR0cHdhdGNoOmY= may look encrypted it is simply a base64 encoded version of <username>:<password>.
// Would be readily available to anyone who could intercept the HTTP request.
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/typescript/npm"
)
type (
// Editor is the alm-tools adaptor.
//
// It holds a logger from the iris' station
// username,password for basic auth
// directory which the client side code is
// keyfile,certfile for TLS listening
// and a host which is listening for
Editor struct {
config *Config
logger func(iris.LogMode, string)
enabled bool // default true
// after alm started
process *os.Process
debugOutput io.Writer
}
)
// New creates and returns an Editor Plugin instance
func New(cfg ...Config) *Editor {
c := DefaultConfig().Merge(cfg)
c.WorkingDir = validateWorkingDir(c.WorkingDir) // add "/" if not exists
return &Editor{
enabled: true,
config: &c,
}
}
// Adapt adapts the editor with Iris.
// Note:
// We use that method and not the return on New because we
// want to export the Editor's functionality to the user.
func (e *Editor) Adapt(frame *iris.Policies) {
policy := iris.EventPolicy{
Build: e.build,
Interrupted: e.close,
}
policy.Adapt(frame)
}
// User set a user, accepts two parameters: username (string), string (string)
func (e *Editor) User(username string, password string) *Editor {
e.config.Username = username
e.config.Password = password
return e
}
func validateWorkingDir(workingDir string) string {
l := workingDir[len(workingDir)-1]
if l != '/' && l != os.PathSeparator {
workingDir += "/"
}
return workingDir
}
// Dir sets the directory which the client side source code alive
func (e *Editor) Dir(workingDir string) *Editor {
e.config.WorkingDir = validateWorkingDir(workingDir)
return e
}
// Port sets the port (int) for the editor adaptor's standalone server
func (e *Editor) Port(port int) *Editor {
e.config.Port = port
return e
}
// SetEnable if true enables the editor adaptor, otherwise disables it
func (e *Editor) SetEnable(enable bool) {
e.enabled = enable
}
// DisableOutput call that if you don't care about alm-tools' messages
// they are useful because that the default configuration shows them
func (e *Editor) DisableOutput() {
e.config.DisableOutput = true
}
// GetDescription EditorPlugin is a bridge between Iris and the alm-tools, the browser-based IDE for client-side sources.
func (e *Editor) GetDescription() string {
return "A bridge between Iris and the alm-tools, the browser-based IDE."
}
// we use that editorWriter to prefix the editor's output with "Editor Adaptor: "
type editorWriter struct {
underline io.Writer
}
// build runs before the server's listens, creates the listener ( use of port parent hostname:DefaultPort if not exist)
func (e *Editor) build(s *iris.Framework) {
e.logger = s.Log
if e.config.Hostname == "" {
e.config.Hostname = iris.ParseHostname(s.Config.VHost)
}
if e.config.Port <= 0 {
e.config.Port = DefaultPort
}
if s, err := filepath.Abs(e.config.WorkingDir); err == nil {
e.config.WorkingDir = s
}
e.start()
}
// close kills the editor's server when Iris is closed
func (e *Editor) close(s *iris.Framework) {
if e.process != nil {
err := e.process.Kill()
if err != nil {
e.logger(iris.DevMode, fmt.Sprintf(`Error while trying to terminate the Editor,
please kill this process by yourself, process id: %d`, e.process.Pid))
}
}
}
// start starts the job
func (e *Editor) start() {
if e.config.Username == "" || e.config.Password == "" {
e.logger(iris.ProdMode, `Error before running alm-tools.
You have to set username & password for security reasons, otherwise this adaptor won't run.`)
return
}
if !npm.NodeModuleExists("alm/bin/alm") {
e.logger(iris.DevMode, "Installing alm-tools, please wait...")
res := npm.NodeModuleInstall("alm")
if res.Error != nil {
e.logger(iris.ProdMode, res.Error.Error())
return
}
e.logger(iris.DevMode, res.Message)
}
cmd := npm.CommandBuilder("node", npm.NodeModuleAbs("alm/src/server.js"))
cmd.AppendArguments("-a", e.config.Username+":"+e.config.Password,
"-h", e.config.Hostname, "-t", strconv.Itoa(e.config.Port), "-d", e.config.WorkingDir[0:len(e.config.WorkingDir)-1])
// for auto-start in the browser: cmd.AppendArguments("-o")
if e.config.KeyFile != "" && e.config.CertFile != "" {
cmd.AppendArguments("--httpskey", e.config.KeyFile, "--httpscert", e.config.CertFile)
}
// when debug is not disabled
// show any messages to the user( they are useful here)
// to the io.Writer that iris' user is defined from configuration
if !e.config.DisableOutput {
outputReader, err := cmd.StdoutPipe()
if err == nil {
outputScanner := bufio.NewScanner(outputReader)
go func() {
for outputScanner.Scan() {
e.logger(iris.DevMode, "Editor: "+outputScanner.Text())
}
}()
errReader, err := cmd.StderrPipe()
if err == nil {
errScanner := bufio.NewScanner(errReader)
go func() {
for errScanner.Scan() {
e.logger(iris.DevMode, "Editor: "+errScanner.Text())
}
}()
}
}
}
err := cmd.Start()
if err != nil {
e.logger(iris.ProdMode, "Error while running alm-tools. Trace: "+err.Error())
return
}
// no need, alm-tools post these
// e.logger.Printf("Editor is running at %s:%d | %s", e.config.Hostname, e.config.Port, e.config.WorkingDir)
}

View File

@ -1,4 +1,4 @@
package utils // #nosec
package npm // #nosec
import (
"fmt"

View File

@ -0,0 +1,124 @@
package npm
import (
"fmt"
"strings"
"time"
)
var (
// nodeModulesPath is the path of the root npm modules
// Ex: C:\\Users\\kataras\\AppData\\Roaming\\npm\\node_modules
nodeModulesPath string
)
type (
// NodeModuleResult holds Message and Error, if error != nil then the npm command has failed
NodeModuleResult struct {
// Message the message (string)
Message string
// Error the error (if any)
Error error
}
)
// NodeModulesPath sets the root directory for the node_modules and returns that
func NodeModulesPath() string {
if nodeModulesPath == "" {
nodeModulesPath = MustCommand("npm", "root", "-g") //here it ends with \n we have to remove it
nodeModulesPath = nodeModulesPath[0 : len(nodeModulesPath)-1]
}
return nodeModulesPath
}
func success(output string, a ...interface{}) NodeModuleResult {
return NodeModuleResult{fmt.Sprintf(output, a...), nil}
}
func fail(errMsg string, a ...interface{}) NodeModuleResult {
return NodeModuleResult{"", fmt.Errorf("\n"+errMsg, a...)}
}
// Output returns the error message if result.Error exists, otherwise returns the result.Message
func (res NodeModuleResult) Output() (out string) {
if res.Error != nil {
out = res.Error.Error()
} else {
out = res.Message
}
return
}
// NodeModuleInstall installs a module
func NodeModuleInstall(moduleName string) NodeModuleResult {
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 := 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)
}
// NodeModuleUnistall removes a module
func NodeModuleUnistall(moduleName string) NodeModuleResult {
out, err := 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)
}
// NodeModuleAbs returns the absolute path of the global node_modules directory + relative
func NodeModuleAbs(relativePath string) string {
return NodeModulesPath() + PathSeparator + strings.Replace(relativePath, "/", PathSeparator, -1)
}
// NodeModuleExists 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 NodeModuleExists(executableRelativePath string) bool {
execAbsPath := NodeModuleAbs(executableRelativePath)
if execAbsPath == "" {
return false
}
return Exists(execAbsPath)
}

View File

@ -0,0 +1,236 @@
package typescript
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/typescript/npm"
)
type (
// TsAdaptor the struct of the Typescript TsAdaptor, holds all necessary fields & methods
TsAdaptor struct {
Config *Config
// taken from framework
logger func(iris.LogMode, string)
}
)
// New creates & returns a new instnace typescript plugin
func New() *TsAdaptor {
c := DefaultConfig()
if !strings.Contains(c.Ignore, nodeModules) {
c.Ignore += "," + nodeModules
}
return &TsAdaptor{Config: &c}
}
// Adapt addapts a TsAdaptor to the Policies via EventPolicy.
// We use that method instead of direct return EventPolicy from new because
// the user should be able to change its configuration from that public API
func (t *TsAdaptor) Adapt(frame *iris.Policies) {
policy := iris.EventPolicy{
Build: t.build,
}
policy.Adapt(frame)
}
func (t *TsAdaptor) build(s *iris.Framework) {
t.logger = s.Log
t.start()
}
//
// implementation
func (t *TsAdaptor) start() {
if t.hasTypescriptFiles() {
//Can't check if permission denied returns always exists = true....
if !npm.NodeModuleExists(t.Config.Bin) {
t.logger(iris.DevMode, "Installing typescript, please wait...")
res := npm.NodeModuleInstall("typescript")
if res.Error != nil {
t.logger(iris.ProdMode, res.Error.Error())
return
}
t.logger(iris.DevMode, res.Message)
}
projects := t.getTypescriptProjects()
if len(projects) > 0 {
watchedProjects := 0
//typescript project (.tsconfig) found
for _, project := range projects {
cmd := npm.CommandBuilder("node", t.Config.Bin, "-p", project[0:strings.LastIndex(project, npm.PathSeparator)]) //remove the /tsconfig.json)
projectConfig, perr := FromFile(project)
if perr != nil {
t.logger(iris.ProdMode, "error while trying to read tsconfig: "+perr.Error())
continue
}
if projectConfig.CompilerOptions.Watch {
watchedProjects++
// if has watch : true then we have to wrap the command to a goroutine (I don't want to use the .Start here)
go func() {
_, err := cmd.Output()
if err != nil {
t.logger(iris.DevMode, err.Error())
return
}
}()
} else {
_, err := cmd.Output()
if err != nil {
t.logger(iris.DevMode, err.Error())
return
}
}
}
t.logger(iris.DevMode, fmt.Sprintf("%d Typescript project(s) compiled ( %d monitored by a background file watcher ) ", len(projects), watchedProjects))
} else {
//search for standalone typescript (.ts) files and compile them
files := t.getTypescriptFiles()
if len(files) > 0 {
watchedFiles := 0
if t.Config.Tsconfig.CompilerOptions.Watch {
watchedFiles = len(files)
}
//it must be always > 0 if we came here, because of if hasTypescriptFiles == true.
for _, file := range files {
absPath, err := filepath.Abs(file)
if err != nil {
continue
}
//these will be used if no .tsconfig found.
// cmd := npm.CommandBuilder("node", t.Config.Bin)
// cmd.Arguments(t.Config.Bin, t.Config.Tsconfig.CompilerArgs()...)
// cmd.AppendArguments(absPath)
compilerArgs := t.Config.Tsconfig.CompilerArgs()
cmd := npm.CommandBuilder("node", t.Config.Bin)
for _, s := range compilerArgs {
cmd.AppendArguments(s)
}
cmd.AppendArguments(absPath)
go func() {
compilerMsgB, _ := cmd.Output()
compilerMsg := string(compilerMsgB)
cmd.Args = cmd.Args[0 : len(cmd.Args)-1] //remove the last, which is the file
if strings.Contains(compilerMsg, "error") {
t.logger(iris.DevMode, compilerMsg)
}
}()
}
t.logger(iris.DevMode, fmt.Sprintf("%d Typescript file(s) compiled ( %d monitored by a background file watcher )", len(files), watchedFiles))
}
}
}
}
func (t *TsAdaptor) hasTypescriptFiles() bool {
root := t.Config.Dir
ignoreFolders := strings.Split(t.Config.Ignore, ",")
hasTs := false
if !npm.Exists(root) {
t.logger(iris.ProdMode, fmt.Sprintf("Typescript Adaptor Error: Directory '%s' couldn't be found,\nplease specify a valid path for your *.ts files", root))
return false
}
// ignore error
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
for i := range ignoreFolders {
if strings.Contains(path, ignoreFolders[i]) {
return nil
}
}
if strings.HasSuffix(path, ".ts") {
hasTs = true
return errors.New("Typescript found, hope that will stop here")
}
return nil
})
return hasTs
}
func (t *TsAdaptor) getTypescriptProjects() []string {
var projects []string
ignoreFolders := strings.Split(t.Config.Ignore, ",")
root := t.Config.Dir
//t.logger.Printf("\nSearching for typescript projects in %s", root)
// ignore error
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
for i := range ignoreFolders {
if strings.Contains(path, ignoreFolders[i]) {
//t.logger.Println(path + " ignored")
return filepath.SkipDir
}
}
if strings.HasSuffix(path, npm.PathSeparator+"tsconfig.json") {
//t.logger.Printf("\nTypescript project found in %s", path)
projects = append(projects, path)
}
return nil
})
return projects
}
// this is being called if getTypescriptProjects return 0 len, then we are searching for files using that:
func (t *TsAdaptor) getTypescriptFiles() []string {
var files []string
ignoreFolders := strings.Split(t.Config.Ignore, ",")
root := t.Config.Dir
// ignore error
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
for i := range ignoreFolders {
if strings.Contains(path, ignoreFolders[i]) {
//t.logger.Println(path + " ignored")
return nil
}
}
if strings.HasSuffix(path, ".ts") {
//t.logger.Printf("\nTypescript file found in %s", path)
files = append(files, path)
}
return nil
})
return files
}
//
//

View File

@ -0,0 +1,93 @@
package main
import (
"encoding/xml"
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/gorillamux"
"gopkg.in/kataras/iris.v6/adaptors/view"
)
// ExampleXML just a test struct to view represents xml content-type
type ExampleXML struct {
XMLName xml.Name `xml:"example"`
One string `xml:"one,attr"`
Two string `xml:"two,attr"`
}
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(gorillamux.New())
app.Get("/data", func(ctx *iris.Context) {
ctx.Data(iris.StatusOK, []byte("Some binary data here."))
})
app.Get("/text", func(ctx *iris.Context) {
ctx.Text(iris.StatusOK, "Plain text here")
})
app.Get("/json", func(ctx *iris.Context) {
ctx.JSON(iris.StatusOK, map[string]string{"hello": "json"}) // or myjsonStruct{hello:"json}
})
app.Get("/jsonp", func(ctx *iris.Context) {
ctx.JSONP(iris.StatusOK, "callbackName", map[string]string{"hello": "jsonp"})
})
app.Get("/xml", func(ctx *iris.Context) {
ctx.XML(iris.StatusOK, ExampleXML{One: "hello", Two: "xml"}) // or iris.Map{"One":"hello"...}
})
app.Get("/markdown", func(ctx *iris.Context) {
ctx.Markdown(iris.StatusOK, "# Hello Dynamic Markdown Iris")
})
app.Adapt(view.HTML("./templates", ".html"))
app.Get("/template", func(ctx *iris.Context) {
ctx.MustRender(
"hi.html", // the file name of the template relative to the './templates'
iris.Map{"Name": "Iris"}, // the .Name inside the ./templates/hi.html
iris.Map{"gzip": false}, // enable gzip for big files
)
})
// ------ first customization without even the need of *Context or a Handler--------
//
// Custom new content-/type:
// app.Adapt(iris.RenderPolicy(func(out io.Writer, name string, binding interface{}, options ...map[string]interface{}) (error, bool) {
// if name == "customcontent-type" {
//
// // some very advanced things here:
// out.Write([]byte(binding.(string)))
// return nil, true
// }
// return nil, false
// }))
//
// app.Get("/custom", func(ctx *iris.Context) {
// ctx.RenderWithStatus(iris.StatusOK, // or MustRender
// "customcontent-type",
// "my custom content here!",
// )
// })
//
// ---- second -----------------------------------------------------------------------
//
// Override the defaults (the json,xml,jsonp,text,data and so on), an existing content-type:
// app.Adapt(iris.RenderPolicy(func(out io.Writer, name string, binding interface{}, options ...map[string]interface{}) (error, bool) {
// if name == "text/plain" {
// out.Write([]byte("From the custom text/plain renderer: " + binding.(string)))
// return nil, true
// }
//
// return nil, false
// }))
// // the context.Text's behaviors was changed now by your custom renderer.
//
app.Listen(":8080")
}

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>Hi Iris</title>
</head>
<body>
<h1>Hi {{.Name}} </h1>
</body>
</html>

View File

@ -0,0 +1,237 @@
// Code generated by go-bindata.
// sources:
// templates/hi.html
// DO NOT EDIT!
package main
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
if clErr != nil {
return nil, err
}
return buf.Bytes(), nil
}
type asset struct {
bytes []byte
info os.FileInfo
}
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
func (fi bindataFileInfo) Name() string {
return fi.name
}
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
func (fi bindataFileInfo) IsDir() bool {
return false
}
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var _templatesHiHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb2\xc9\x28\xc9\xcd\xb1\xe3\xe5\xb2\xc9\x48\x4d\x4c\x01\xd1\x25\x99\x25\x39\xa9\x76\x1e\x99\x0a\x9e\x45\x99\xc5\x0a\xd1\x21\x1e\xae\x0a\x21\x9e\x21\x3e\xae\xb1\x36\xfa\x10\x29\xa0\x1a\x7d\x98\xe2\xa4\xfc\x94\x4a\x20\xcd\x69\x93\x61\x08\xd2\x52\x5d\xad\xe7\x97\x98\x9b\x5a\x5b\x0b\x52\x03\x95\x03\x2a\x86\xd8\x00\x08\x00\x00\xff\xff\xed\x0e\xad\x42\x6a\x00\x00\x00")
func templatesHiHtmlBytes() ([]byte, error) {
return bindataRead(
_templatesHiHtml,
"templates/hi.html",
)
}
func templatesHiHtml() (*asset, error) {
bytes, err := templatesHiHtmlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/hi.html", size: 106, mode: os.FileMode(438), modTime: time.Unix(1468907204, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"templates/hi.html": templatesHiHtml,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"templates": {nil, map[string]*bintree{
"hi.html": {templatesHiHtml, map[string]*bintree{}},
}},
}}
// RestoreAsset restores an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// RestoreAssets restores an asset under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}

View File

@ -0,0 +1,24 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/view"
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New())
//$ go-bindata ./templates/...
// templates are not used, you can delete the folder and run the example
app.Adapt(view.HTML("./templates", ".html").Binary(Asset, AssetNames))
app.Get("/hi", hi)
app.Listen(":8080")
}
func hi(ctx *iris.Context) {
ctx.MustRender("hi.html", struct{ Name string }{Name: "iris"})
}

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>Hi Iris [THE TITLE]</title>
</head>
<body>
<h1>Hi {{.Name}}
</body>
</html>

View File

@ -0,0 +1,23 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/view"
)
func main() {
app := iris.New(iris.Configuration{Gzip: false, Charset: "UTF-8"}) // defaults to these
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New())
app.Adapt(view.HTML("./templates", ".html"))
app.Get("/hi", hi)
app.Listen(":8080")
}
func hi(ctx *iris.Context) {
ctx.MustRender("hi.html", struct{ Name string }{Name: "iris"})
}

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>Hi Iris</title>
</head>
<body>
<h1>Hi {{.Name}} </h1>
</body>
</html>

View File

@ -0,0 +1,32 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/view"
)
type mypage struct {
Title string
Message string
}
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New())
tmpl := view.HTML("./templates", ".html")
tmpl.Layout("layout.html")
app.Adapt(tmpl)
app.Get("/", func(ctx *iris.Context) {
ctx.Render("mypage.html", mypage{"My Page title", "Hello world!"}, iris.Map{"gzip": true})
// Note that: you can pass "layout" : "otherLayout.html" to bypass the config's Layout property
// or iris.NoLayout to disable layout on this render action.
// third is an optional parameter
})
app.Listen(":8080")
}

View File

@ -0,0 +1,11 @@
<html>
<head>
<title>My Layout</title>
</head>
<body>
<h1>Body is:</h1>
<!-- Render the current template here -->
{{ yield }}
</body>
</html>

View File

@ -0,0 +1,4 @@
<h1>
Title: {{.Title}}
</h1>
<h3>Message: {{.Message}} </h3>

View File

@ -0,0 +1,3 @@
## Info
This folder examines the {{render "dir/templatefilename"}} functionality to manually render any template inside any template

View File

@ -0,0 +1,49 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/view"
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New())
tmpl := view.HTML("./templates", ".html")
tmpl.Layout("layouts/layout.html")
tmpl.Funcs(map[string]interface{}{
"greet": func(s string) string {
return "Greetings " + s + "!"
},
})
app.Adapt(tmpl)
app.Get("/", func(ctx *iris.Context) {
if err := ctx.Render("page1.html", nil); err != nil {
println(err.Error())
}
})
// remove the layout for a specific route
app.Get("/nolayout", func(ctx *iris.Context) {
if err := ctx.Render("page1.html", nil, iris.RenderOptions{"layout": iris.NoLayout}); err != nil {
println(err.Error())
}
})
// set a layout for a party, .Layout should be BEFORE any Get or other Handle party's method
my := app.Party("/my").Layout("layouts/mylayout.html")
{
my.Get("/", func(ctx *iris.Context) {
ctx.MustRender("page1.html", nil)
})
my.Get("/other", func(ctx *iris.Context) {
ctx.MustRender("page1.html", nil)
})
}
app.Listen(":8080")
}

View File

@ -0,0 +1,12 @@
<html>
<head>
<title>Layout</title>
</head>
<body>
<h1>This is the global layout</h1>
<br />
<!-- Render the current template here -->
{{ yield }}
</body>
</html>

View File

@ -0,0 +1,12 @@
<html>
<head>
<title>my Layout</title>
</head>
<body>
<h1>This is the layout for the /my/ and /my/other routes only</h1>
<br />
<!-- Render the current template here -->
{{ yield }}
</body>
</html>

View File

@ -0,0 +1,7 @@
<div style="background-color: black; color: blue">
<h1>Page 1 {{ greet "iris developer"}}</h1>
{{ render "partials/page1_partial1.html"}}
</div>

View File

@ -0,0 +1,3 @@
<div style="background-color: white; color: red">
<h1>Page 1's Partial 1</h1>
</div>

View File

@ -0,0 +1,53 @@
// Package main an example on how to naming your routes & use the custom 'url' HTML Template Engine, same for other template engines.
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/gorillamux"
"gopkg.in/kataras/iris.v6/adaptors/view"
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(gorillamux.New())
app.Adapt(view.HTML("./templates", ".html"))
app.Get("/mypath", emptyHandler).ChangeName("my-page1")
app.Get("/mypath2/{param1}/{param2}", emptyHandler).ChangeName("my-page2")
app.Get("/mypath3/{param1}/statichere/{param2}", emptyHandler).ChangeName("my-page3")
app.Get("/mypath4/{param1}/statichere/{param2}/{otherparam}/{something:.*}", emptyHandler).ChangeName("my-page4")
// same with Handle/Func
app.HandleFunc("GET", "/mypath5/{param1}/statichere/{param2}/{otherparam}/anything/{something:.*}", emptyHandler).ChangeName("my-page5")
app.Get("/mypath6/{param1}/{param2}/staticParam/{param3AfterStatic}", emptyHandler).ChangeName("my-page6")
app.Get("/", func(ctx *iris.Context) {
// for /mypath6...
paramsAsArray := []string{"param1", "theParam1",
"param2", "theParam2",
"param3AfterStatic", "theParam3"}
if err := ctx.Render("page.html", iris.Map{"ParamsAsArray": paramsAsArray}); err != nil {
panic(err)
}
})
app.Get("/redirect/{namedRoute}", func(ctx *iris.Context) {
routeName := ctx.Param("namedRoute")
println("The full uri of " + routeName + "is: " + app.URL(routeName))
// if routeName == "my-page1"
// prints: The full uri of my-page1 is: http://127.0.0.1:8080/mypath
ctx.RedirectTo(routeName)
// http://127.0.0.1:8080/redirect/my-page1 will redirect to -> http://127.0.0.1:8080/mypath
})
app.Listen("localhost:8080")
}
func emptyHandler(ctx *iris.Context) {
ctx.Writef("Hello from %s.", ctx.Path())
}

View File

@ -0,0 +1,27 @@
<a href="{{url "my-page1"}}">http://127.0.0.1:8080/mypath</a>
<br />
<br />
<a href="{{url "my-page2" "param1" "theParam1" "param2" "theParam2"}}">http://localhost:8080/mypath2/:param1/:param2</a>
<br />
<br />
<a href="{{url "my-page3" "param1" "theParam1" "param2" "theParam2AfterStatic"}}">
http://localhost:8080/mypath3/:param1/statichere/:param2</a>
<br />
<br />
<a href="{{url "my-page4" "param1" "theParam1" "param2" "theparam2AfterStatic" "otherparam" "otherParam" "something" "matchAnything"}}">http://localhost/mypath4/:param1/statichere/:param2/:otherparam/*something</a>
<br />
<br />
<a href="{{url "my-page5" "param1" "theParam1" "param2" "theParam2AfterStatic" "otherparam" "otherParam" "something" "matchAnythingAfterStatic"}}">
http://localhost:8080/mypath5/:param1/statichere/:param2/:otherparam/anything/*anything</a>
<br />
<br />
<a href={{url "my-page6" .ParamsAsArray }}>http://localhost:8080/mypath6/{param1}/{param2}/staticParam/{param3AfterStatic}</a>

View File

@ -0,0 +1,32 @@
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
# 102.54.94.97 rhino.acme.com # source server
# 38.25.63.10 x.acme.com # x client host
# localhost name resolution is handled within DNS itself.
127.0.0.1 localhost
::1 localhost
#-IRIS-For development machine, you have to configure your dns also for online, search google how to do it if you don't know
127.0.0.1 username1.127.0.0.1
127.0.0.1 username2.127.0.0.1
127.0.0.1 username3.127.0.0.1
127.0.0.1 username4.127.0.0.1
127.0.0.1 username5.127.0.0.1
# note that you can always use custom subdomains
#-END IRIS-
# Windows: Drive:/Windows/system32/drivers/etc/hosts, on Linux: /etc/hosts

View File

@ -0,0 +1,53 @@
// Package main an example on how to naming your routes & use the custom 'url' HTML Template Engine, same for other template engines.
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/gorillamux"
"gopkg.in/kataras/iris.v6/adaptors/view"
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(gorillamux.New())
app.Adapt(view.HTML("./templates", ".html"))
app.Get("/mypath", emptyHandler).ChangeName("my-page1")
app.Get("/mypath2/{param1}/{param2}", emptyHandler).ChangeName("my-page2")
app.Get("/mypath3/{param1}/statichere/{param2}", emptyHandler).ChangeName("my-page3")
app.Get("/mypath4/{param1}/statichere/{param2}/{otherparam}/{something:.*}", emptyHandler).ChangeName("my-page4")
// same with Handle/Func
app.HandleFunc("GET", "/mypath5/{param1}/statichere/{param2}/{otherparam}/anything/{something:.*}", emptyHandler).ChangeName("my-page5")
app.Get("/mypath6/{param1}/{param2}/staticParam/{param3AfterStatic}", emptyHandler).ChangeName("my-page6")
app.Get("/", func(ctx *iris.Context) {
// for /mypath6...
paramsAsArray := []string{"param1", "theParam1",
"param2", "theParam2",
"param3AfterStatic", "theParam3"}
if err := ctx.Render("page.html", iris.Map{"ParamsAsArray": paramsAsArray}); err != nil {
panic(err)
}
})
app.Get("/redirect/{namedRoute}", func(ctx *iris.Context) {
routeName := ctx.Param("namedRoute")
println("The full uri of " + routeName + "is: " + app.URL(routeName))
// if routeName == "my-page1"
// prints: The full uri of my-page1 is: http://127.0.0.1:8080/mypath
ctx.RedirectTo(routeName)
// http://127.0.0.1:8080/redirect/my-page1 will redirect to -> http://127.0.0.1:8080/mypath
})
app.Listen("localhost:8080")
}
func emptyHandler(ctx *iris.Context) {
ctx.Writef("Hello from %s.", ctx.Path())
}

View File

@ -0,0 +1,16 @@
<!-- the only difference between normal named routes and dynamic subdomains named routes is that the first argument of url
is the subdomain part instead of named parameter-->
<a href="{{url "dynamic-subdomain1" "username1"}}">username1.127.0.0.1:8080/mypath</a>
<br />
<br />
<a href="{{url "dynamic-subdomain2" "username2" "theParam1" "theParam2"}}">username2.127.0.0.1:8080/mypath2/:param1/:param2</a>
<br />
<br />
<a href="{{url "dynamic-subdomain3" "username3" "theParam1" "theParam2AfterStatic"}}">username3.127.0.0.1:8080/mypath3/:param1/statichere/:param2</a>
<br />
<br />
<a href="{{url "dynamic-subdomain4" "username4" "theParam1" "theparam2AfterStatic" "otherParam" "matchAnything"}}">username4.127.0.0.1:8080/mypath4/:param1/statichere/:param2/:otherparam/*something</a>
<br />
<br />
<a href="{{url "dynamic-subdomain5" .ParamsAsArray }}" >username5.127.0.0.1:8080/mypath6/:param1/:param2/staticParam/:param3AfterStatic</a>

136
adaptors/view/adaptor.go Normal file
View File

@ -0,0 +1,136 @@
package view
import (
"io"
"strings"
"github.com/kataras/go-template"
"gopkg.in/kataras/iris.v6"
)
// Adaptor contains the common actions
// that all template engines share.
//
// We need to export that as it is without an interface
// because it may be used as a wrapper for a template engine
// that is not exists yet but community can create.
type Adaptor struct {
dir string
extension string
// for a .go template file lives inside the executable
assetFn func(name string) ([]byte, error)
namesFn func() []string
reload bool
engine template.Engine // used only on Adapt, we could make
//it as adaptEngine and pass a second parameter there but this would break the pattern.
}
// NewAdaptor returns a new general template engine policy adaptor.
func NewAdaptor(directory string, extension string, e template.Engine) *Adaptor {
return &Adaptor{
dir: directory,
extension: extension,
engine: e,
}
}
// Binary optionally, use it when template files are distributed
// inside the app executable (.go generated files).
func (h *Adaptor) Binary(assetFn func(name string) ([]byte, error), namesFn func() []string) *Adaptor {
h.assetFn = assetFn
h.namesFn = namesFn
return h
}
// Reload if setted to true the templates are reloading on each call,
// use it when you're in development and you're boring of restarting
// the whole app when you edit a template file
func (h *Adaptor) Reload(developmentMode bool) *Adaptor {
h.reload = developmentMode
return h
}
// Adapt adapts a template engine to the main Iris' policies.
// this specific Adapt is a multi-policies adaptors
// we use that instead of just return New() iris.RenderPolicy
// for two reasons:
// - the user may need to edit the adaptor's fields
// like Directory, Binary
// - we need to adapt an event policy to add the engine to the external mux
// and load it.
func (h *Adaptor) Adapt(frame *iris.Policies) {
mux := template.DefaultMux
// on the build state in order to have the shared funcs also
evt := iris.EventPolicy{
Build: func(s *iris.Framework) {
// mux has default to ./templates and .html ext
// no need for any checks here.
// the RenderPolicy will give a "no templates found on 'directory'"
// if user tries to use the context.Render without having the template files
// no need to panic here because we will use the html as the default template engine
// even if the user doesn't asks for
// or no? we had the defaults before... maybe better to give the user
// the oportunity to learn about the template's configuration
// (now 6.1.4 ) they are defaulted and users may don't know how and if they can change the configuration
// even if the book and examples covers these things, many times they're asking me on chat.............
// so no defaults? ok no defaults. This file then will be saved to /adaptors as with other template engines.
// simple.
mux.AddEngine(h.engine).
Directory(h.dir, h.extension).
Binary(h.assetFn, h.namesFn)
mux.Reload = h.reload
// notes for me: per-template engine funcs are setted by each template engine adaptor itself,
// here we will set the template funcs policy'.
// as I explain on the TemplateFuncsPolicy it exists in order to allow community to create plugins
// even by adding custom template funcs to their behaviors.
// We know that iris.TemplateFuncsPolicy is useless without this specific
// adaptor. We also know that it is not a good idea to have two
// policies with the same function or we can? wait. No we can't.
// We can't because:
// - the RenderPolicy should accept any type of render process, not only tempaltes.
// - I didn't design iris/policy.go to keep logic about implementation, this would make that very limited
// and I don't want to break that just for the templates.
// - We want community to be able to create packages which can adapt template functions but
// not to worry about the rest of the template engine adaptor policy.
// And even don't worry if the user has registered or use a template engine at all.
// So we should keep separate the TemplateFuncsPolicy(just map[string]interface{})
// from the rest of the implementation.
//
// So when the community wants to create a template adaptor has two options:
// - Use the RenderPolicy which is just a func
// - Use the kataras/iris/adaptors/view.Adaptor adaptor wrapper for RenderPolicy with a compination of kataras/go-template/.Engine
//
//
// So here is the only place we adapt the iris.TemplateFuncsPolicy to the tempaltes, if and only if templates are used,
// otherwise they are just ignored without any creepy things.
//
// The TemplateFuncsPolicy will work in compination with the specific template adaptor's functions(see html.go and the rest)
if len(frame.TemplateFuncsPolicy) > 0 {
mux.SetFuncMapToEngine(frame.TemplateFuncsPolicy, h.engine)
}
if err := mux.Load(); err != nil {
s.Log(iris.ProdMode, err.Error())
}
},
}
// adapt the build event to the main policies
evt.Adapt(frame)
r := iris.RenderPolicy(func(out io.Writer, file string, tmplContext interface{}, options ...map[string]interface{}) (error, bool) {
// template mux covers that but maybe we have more than one RenderPolicy
// and each of them carries a different mux on the new design.
if strings.Contains(file, h.extension) {
return mux.ExecuteWriter(out, file, tmplContext, options...), true
}
return nil, false
})
r.Adapt(frame)
}

48
adaptors/view/amber.go Normal file
View File

@ -0,0 +1,48 @@
package view
import (
"github.com/kataras/go-template/amber"
)
// AmberAdaptor is the adaptor for the Amber, simple, engine.
// Read more about the Amber Go Template at:
// https://github.com/eknkc/amber
// and https://github.com/kataras/go-template/tree/master/amber
type AmberAdaptor struct {
*Adaptor
engine *amber.Engine
}
// Amber returns a new kataras/go-template/amber template engine
// with the same features as all iris' view engines have:
// Binary assets load (templates inside your executable with .go extension)
// Layout, Funcs, {{ url }} {{ urlpath}} for reverse routing and much more.
//
// Read more: https://github.com/eknkc/amber
func Amber(directory string, extension string) *AmberAdaptor {
e := amber.New()
return &AmberAdaptor{
Adaptor: NewAdaptor(directory, extension, e),
engine: e,
}
}
// Funcs adds the elements of the argument map to the template's function map.
// It is legal to overwrite elements of the default actions:
// - url func(routeName string, args ...string) string
// - urlpath func(routeName string, args ...string) string
// - render func(fullPartialName string) (template.HTML, error).
func (a *AmberAdaptor) Funcs(funcMap map[string]interface{}) *AmberAdaptor {
if len(funcMap) == 0 {
return a
}
// configuration maps are never nil, because
// they are initialized at each of the engine's New func
// so we're just passing them inside it.
for k, v := range funcMap {
a.engine.Config.Funcs[k] = v
}
return a
}

93
adaptors/view/django.go Normal file
View File

@ -0,0 +1,93 @@
package view
import (
"github.com/kataras/go-template/django"
)
type (
// Value conversion for django.Value
Value django.Value
// Error conversion for django.Error
Error django.Error
// FilterFunction conversion for django.FilterFunction
FilterFunction func(in *Value, param *Value) (out *Value, err *Error)
)
// this exists because of moving the pongo2 to the vendors without conflictitions if users
// wants to register pongo2 filters they can use this django.FilterFunc to do so.
func convertFilters(djangoFilters map[string]FilterFunction) map[string]django.FilterFunction {
filters := make(map[string]django.FilterFunction, len(djangoFilters))
for k, v := range djangoFilters {
func(filterName string, filterFunc FilterFunction) {
fn := django.FilterFunction(func(in *django.Value, param *django.Value) (*django.Value, *django.Error) {
theOut, theErr := filterFunc((*Value)(in), (*Value)(param))
return (*django.Value)(theOut), (*django.Error)(theErr)
})
filters[filterName] = fn
}(k, v)
}
return filters
}
// DjangoAdaptor is the adaptor for the Django engine.
// Read more about the Django Go Template at:
// https://github.com/flosch/pongo2
// and https://github.com/kataras/go-template/tree/master/django
type DjangoAdaptor struct {
*Adaptor
engine *django.Engine
filters map[string]FilterFunction
}
// Django returns a new kataras/go-template/django template engine
// with the same features as all iris' view engines have:
// Binary assets load (templates inside your executable with .go extension)
// Layout, Funcs, {{ url }} {{ urlpath}} for reverse routing and much more.
//
// Read more: https://github.com/flosch/pongo2
func Django(directory string, extension string) *DjangoAdaptor {
e := django.New()
return &DjangoAdaptor{
Adaptor: NewAdaptor(directory, extension, e),
engine: e,
filters: make(map[string]FilterFunction, 0),
}
}
// Filters for pongo2, map[name of the filter] the filter function .
//
// Note, these Filters function overrides ALL the previous filters
// It SETS a new filter map based on the given 'filtersMap' parameter.
func (d *DjangoAdaptor) Filters(filtersMap map[string]FilterFunction) *DjangoAdaptor {
if len(filtersMap) == 0 {
return d
}
// configuration maps are never nil, because
// they are initialized at each of the engine's New func
// so we're just passing them inside it.
filters := convertFilters(filtersMap)
d.engine.Config.Filters = filters
return d
}
// Globals share context fields between templates. https://github.com/flosch/pongo2/issues/35
func (d *DjangoAdaptor) Globals(globalsMap map[string]interface{}) *DjangoAdaptor {
if len(globalsMap) == 0 {
return d
}
for k, v := range globalsMap {
d.engine.Config.Globals[k] = v
}
return d
}
// DebugTemplates enables template debugging.
// The verbose error messages will appear in browser instead of quiet passes with error code.
func (d *DjangoAdaptor) DebugTemplates(debug bool) *DjangoAdaptor {
d.engine.Config.DebugTemplates = debug
return d
}

View File

@ -0,0 +1,62 @@
package view
import (
"github.com/kataras/go-template/handlebars"
)
// HandlebarsAdaptor is the adaptor for the Handlebars engine.
// Read more about the Handlebars Go Template at:
// https://github.com/aymerick/raymond
// and https://github.com/kataras/go-template/tree/master/handlebars
type HandlebarsAdaptor struct {
*Adaptor
engine *handlebars.Engine
}
// Handlebars returns a new kataras/go-template/handlebars template engine
// with the same features as all iris' view engines have:
// Binary assets load (templates inside your executable with .go extension)
// Layout, Funcs, {{ url }} {{ urlpath}} for reverse routing and much more.
//
// Read more: https://github.com/aymerick/raymond
func Handlebars(directory string, extension string) *HandlebarsAdaptor {
e := handlebars.New()
return &HandlebarsAdaptor{
Adaptor: NewAdaptor(directory, extension, e),
engine: e,
}
}
// Layout sets the layout template file which inside should use
// the {{ yield }} func to yield the main template file
// and optionally {{partial/partial_r/render}} to render other template files like headers and footers
//
// The 'tmplLayoutFile' is a relative path of the templates base directory,
// for the template file whith its extension.
//
// Example: Handlebars("./templates", ".html").Layout("layouts/mainLayout.html")
// // mainLayout.html is inside: "./templates/layouts/".
//
// Note: Layout can be changed for a specific call
// action with the option: "layout" on the Iris' context.Render function.
func (h *HandlebarsAdaptor) Layout(tmplLayoutFile string) *HandlebarsAdaptor {
h.engine.Config.Layout = tmplLayoutFile
return h
}
// Funcs adds the elements of the argument map to the template's function map.
// It is legal to overwrite elements of the default actions:
// - url func(routeName string, args ...string) string
// - urlpath func(routeName string, args ...string) string
// - and handlebars specific, read more: https://github.com/aymerick/raymond.
func (h *HandlebarsAdaptor) Funcs(funcMap map[string]interface{}) *HandlebarsAdaptor {
if funcMap == nil {
return h
}
for k, v := range funcMap {
h.engine.Config.Helpers[k] = v
}
return h
}

92
adaptors/view/html.go Normal file
View File

@ -0,0 +1,92 @@
package view
import (
"github.com/kataras/go-template/html"
)
// HTMLAdaptor is the html engine policy adaptor
// used like the "html/template" standard go package
// but with a lot of extra features by.
//
// This is just a wrapper of kataras/go-template/html.
type HTMLAdaptor struct {
*Adaptor
engine *html.Engine
}
// HTML creates and returns a new kataras/go-template/html engine.
// The html engine used like the "html/template" standard go package
// but with a lot of extra features.
func HTML(directory string, extension string) *HTMLAdaptor {
engine := html.New()
return &HTMLAdaptor{
Adaptor: NewAdaptor(directory, extension, engine),
// create the underline engine with the default configuration,
// which can be changed by this type.
//The whole funcs should called only before Iris' build & listen state.
engine: engine, // we need that for the configuration only.
}
}
// Delims sets the action delimiters to the specified strings, to be used in
// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
// definitions will inherit the settings. An empty delimiter stands for the
// corresponding default: {{ or }}.
func (h *HTMLAdaptor) Delims(left string, right string) *HTMLAdaptor {
if left != "" && right != "" {
h.engine.Config.Left = left
h.engine.Config.Right = right
}
return h
}
// Layout sets the layout template file which inside should use
// the {{ yield }} func to yield the main template file
// and optionally {{partial/partial_r/render}} to render other template files like headers and footers
//
// The 'tmplLayoutFile' is a relative path of the templates base directory,
// for the template file whith its extension.
//
// Example: HTML("./templates", ".html").Layout("layouts/mainLayout.html")
// // mainLayout.html is inside: "./templates/layouts/".
//
// Note: Layout can be changed for a specific call
// action with the option: "layout" on the Iris' context.Render function.
func (h *HTMLAdaptor) Layout(tmplLayoutFile string) *HTMLAdaptor {
h.engine.Config.Layout = tmplLayoutFile
return h
}
// LayoutFuncs adds the elements of the argument map to the template's layout-only function map.
// It is legal to overwrite elements of the default layout actions:
// - yield func() (template.HTML, error)
// - current func() (string, error)
// - partial func(partialName string) (template.HTML, error)
// - partial_r func(partialName string) (template.HTML, error)
// - render func(fullPartialName string) (template.HTML, error).
func (h *HTMLAdaptor) LayoutFuncs(funcMap map[string]interface{}) *HTMLAdaptor {
if funcMap != nil {
h.engine.Config.LayoutFuncs = funcMap
}
return h
}
// Funcs adds the elements of the argument map to the template's function map.
// It is legal to overwrite elements of the default actions:
// - url func(routeName string, args ...string) string
// - urlpath func(routeName string, args ...string) string
// - render func(fullPartialName string) (template.HTML, error).
func (h *HTMLAdaptor) Funcs(funcMap map[string]interface{}) *HTMLAdaptor {
if len(funcMap) == 0 {
return h
}
// configuration maps are never nil, because
// they are initialized at each of the engine's New func
// so we're just passing them inside it.
for k, v := range funcMap {
h.engine.Config.Funcs[k] = v
}
return h
}

21
adaptors/view/pug.go Normal file
View File

@ -0,0 +1,21 @@
package view
import (
"github.com/Joker/jade"
)
// Pug (or Jade) returns a new kataras/go-template/pug engine.
// It shares the same exactly logic with the
// HTMLAdaptor, it uses the same exactly configuration.
// It has got some features and a lot of functions
// which will make your life easier.
// Read more about the Jade Go Template: https://github.com/Joker/jade
func Pug(directory string, extension string) *HTMLAdaptor {
h := HTML(directory, extension)
// because I has designed the kataras/go-template
// so powerful, we can just pass a parser middleware
// into the standard html template engine
// and we're ready to go.
h.engine.Middleware = jade.Parse
return h
}

299
addr.go Normal file
View File

@ -0,0 +1,299 @@
package iris
import (
"crypto/tls"
"net"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/kataras/go-errors"
"golang.org/x/crypto/acme/autocert"
)
var (
errPortAlreadyUsed = errors.New("Port is already used")
errRemoveUnix = errors.New("Unexpected error when trying to remove unix socket file. Addr: %s | Trace: %s")
errChmod = errors.New("Cannot chmod %#o for %q: %s")
errCertKeyMissing = errors.New("You should provide certFile and keyFile for TLS/SSL")
errParseTLS = errors.New("Couldn't load TLS, certFile=%q, keyFile=%q. Trace: %s")
)
// TCP4 returns a new tcp4 Listener
func TCP4(addr string) (net.Listener, error) {
return net.Listen("tcp4", ParseHost(addr))
}
// TCPKeepAlive returns a new tcp4 keep alive Listener
func TCPKeepAlive(addr string) (net.Listener, error) {
ln, err := TCP4(addr)
if err != nil {
return nil, err
}
return TCPKeepAliveListener{ln.(*net.TCPListener)}, err
}
// UNIX returns a new unix(file) Listener
func UNIX(addr string, mode os.FileMode) (net.Listener, error) {
if errOs := os.Remove(addr); errOs != nil && !os.IsNotExist(errOs) {
return nil, errRemoveUnix.Format(addr, errOs.Error())
}
listener, err := net.Listen("unix", addr)
if err != nil {
return nil, errPortAlreadyUsed.AppendErr(err)
}
if err = os.Chmod(addr, mode); err != nil {
return nil, errChmod.Format(mode, addr, err.Error())
}
return listener, nil
}
// TLS returns a new TLS Listener
func TLS(addr, certFile, keyFile string) (net.Listener, error) {
if certFile == "" || keyFile == "" {
return nil, errCertKeyMissing
}
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, errParseTLS.Format(certFile, keyFile, err)
}
return CERT(addr, cert)
}
// CERT returns a listener which contans tls.Config with the provided certificate, use for ssl
func CERT(addr string, cert tls.Certificate) (net.Listener, error) {
ln, err := TCP4(addr)
if err != nil {
return nil, err
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
PreferServerCipherSuites: true,
}
return tls.NewListener(ln, tlsConfig), nil
}
// LETSENCRYPT returns a new Automatic TLS Listener using letsencrypt.org service
// receives two parameters, the first is the domain of the server
// and the second is optionally, the cache directory, if you skip it then the cache directory is "./certcache"
// if you want to disable cache directory then simple give it a value of empty string ""
//
// does NOT supports localhost domains for testing.
//
// this is the recommended function to use when you're ready for production state
func LETSENCRYPT(addr string, cacheDirOptional ...string) (net.Listener, error) {
if portIdx := strings.IndexByte(addr, ':'); portIdx == -1 {
addr += ":443"
}
ln, err := TCP4(addr)
if err != nil {
return nil, err
}
cacheDir := "./certcache"
if len(cacheDirOptional) > 0 {
cacheDir = cacheDirOptional[0]
}
m := autocert.Manager{
Prompt: autocert.AcceptTOS,
} // HostPolicy is missing, if user wants it, then she/he should manually
// configure the autocertmanager and use the `iris.Default.Serve` to pass that listener
if cacheDir == "" {
// then the user passed empty by own will, then I guess she/he doesnt' want any cache directory
} else {
m.Cache = autocert.DirCache(cacheDir)
}
tlsConfig := &tls.Config{GetCertificate: m.GetCertificate}
tlsLn := tls.NewListener(ln, tlsConfig)
return tlsLn, nil
}
// TCPKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections.
// Dead TCP connections (e.g. closing laptop mid-download) eventually
// go away
// It is not used by default if you want to pass a keep alive listener
// then just pass the child listener, example:
// listener := iris.TCPKeepAliveListener{iris.TCP4(":8080").(*net.TCPListener)}
type TCPKeepAliveListener struct {
*net.TCPListener
}
// Accept implements the listener and sets the keep alive period which is 3minutes
func (ln TCPKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
err = tc.SetKeepAlive(true)
if err != nil {
return
}
err = tc.SetKeepAlivePeriod(3 * time.Minute)
if err != nil {
return
}
return tc, nil
}
///TODO:
// func (ln TCPKeepAliveListener) Close() error {
// return nil
// }
// ParseHost tries to convert a given string to an address which is compatible with net.Listener and server
func ParseHost(addr string) string {
// check if addr has :port, if not do it +:80 ,we need the hostname for many cases
a := addr
if a == "" {
// check for os environments
if oshost := os.Getenv("ADDR"); oshost != "" {
a = oshost
} else if oshost := os.Getenv("HOST"); oshost != "" {
a = oshost
} else if oshost := os.Getenv("HOSTNAME"); oshost != "" {
a = oshost
// check for port also here
if osport := os.Getenv("PORT"); osport != "" {
a += ":" + osport
}
} else if osport := os.Getenv("PORT"); osport != "" {
a = ":" + osport
} else {
a = ":http"
}
}
if portIdx := strings.IndexByte(a, ':'); portIdx == 0 {
if a[portIdx:] == ":https" {
a = DefaultServerHostname + ":443"
} else {
// if contains only :port ,then the : is the first letter, so we dont have setted a hostname, lets set it
a = DefaultServerHostname + a
}
}
/* changed my mind, don't add 80, this will cause problems on unix listeners, and it's not really necessary because we take the port using parsePort
if portIdx := strings.IndexByte(a, ':'); portIdx < 0 {
// missing port part, add it
a = a + ":80"
}*/
return a
}
// ParseHostname receives an addr of form host[:port] and returns the hostname part of it
// ex: localhost:8080 will return the `localhost`, mydomain.com:8080 will return the 'mydomain'
func ParseHostname(addr string) string {
idx := strings.IndexByte(addr, ':')
if idx == 0 {
// only port, then return 0.0.0.0
return "0.0.0.0"
} else if idx > 0 {
return addr[0:idx]
}
// it's already hostname
return addr
}
// ParsePort receives an addr of form host[:port] and returns the port part of it
// ex: localhost:8080 will return the `8080`, mydomain.com will return the '80'
func ParsePort(addr string) int {
if portIdx := strings.IndexByte(addr, ':'); portIdx != -1 {
afP := addr[portIdx+1:]
p, err := strconv.Atoi(afP)
if err == nil {
return p
} else if afP == "https" { // it's not number, check if it's :https
return 443
}
}
return 80
}
const (
// SchemeHTTPS returns "https://" (full)
SchemeHTTPS = "https://"
// SchemeHTTP returns "http://" (full)
SchemeHTTP = "http://"
)
// ParseScheme returns the scheme based on the host,addr,domain
// Note: the full scheme not just http*,https* *http:// *https://
func ParseScheme(domain string) string {
// pure check
if strings.HasPrefix(domain, SchemeHTTPS) || ParsePort(domain) == 443 {
return SchemeHTTPS
}
return SchemeHTTP
}
// ProxyHandler returns a new net/http.Handler which works as 'proxy', maybe doesn't suits you look its code before using that in production
var ProxyHandler = func(redirectSchemeAndHost string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// override the handler and redirect all requests to this addr
redirectTo := redirectSchemeAndHost
fakehost := r.URL.Host
path := r.URL.EscapedPath()
if strings.Count(fakehost, ".") >= 3 { // propably a subdomain, pure check but doesn't matters don't worry
if sufIdx := strings.LastIndexByte(fakehost, '.'); sufIdx > 0 {
// check if the last part is a number instead of .com/.gr...
// if it's number then it's propably is 0.0.0.0 or 127.0.0.1... so it shouldn' use subdomain
if _, err := strconv.Atoi(fakehost[sufIdx+1:]); err != nil {
// it's not number then process the try to parse the subdomain
redirectScheme := ParseScheme(redirectSchemeAndHost)
realHost := strings.Replace(redirectSchemeAndHost, redirectScheme, "", 1)
redirectHost := strings.Replace(fakehost, fakehost, realHost, 1)
redirectTo = redirectScheme + redirectHost + path
http.Redirect(w, r, redirectTo, StatusMovedPermanently)
return
}
}
}
if path != "/" {
redirectTo += path
}
if redirectTo == r.URL.String() {
return
}
// redirectTo := redirectSchemeAndHost + r.RequestURI
http.Redirect(w, r, redirectTo, StatusMovedPermanently)
}
}
// Proxy not really a proxy, it's just
// starts a server listening on proxyAddr but redirects all requests to the redirectToSchemeAndHost+$path
// nothing special, use it only when you want to start a secondary server which its only work is to redirect from one requested path to another
//
// returns a close function
func Proxy(proxyAddr string, redirectSchemeAndHost string) func() error {
proxyAddr = ParseHost(proxyAddr)
// override the handler and redirect all requests to this addr
h := ProxyHandler(redirectSchemeAndHost)
prx := New(OptionDisableBanner(true))
prx.Adapt(RouterBuilderPolicy(func(RouteRepository, ContextPool) http.Handler {
return h
}))
go prx.Listen(proxyAddr)
time.Sleep(300 * time.Millisecond)
return func() error { return prx.Close() }
}

View File

@ -2,11 +2,9 @@ package iris
import (
"crypto/tls"
"io"
"net"
"net/http"
"net/url"
"os"
"strconv"
"time"
@ -39,7 +37,7 @@ func (o OptionSet) Set(c *Configuration) {
o(c)
}
// Configuration the whole configuration for an iris instance ($instance.Config) or global iris instance (iris.Config)
// Configuration the whole configuration for an iris instance ($instance.Config) or global iris instance (iris.Default.Config)
// these can be passed via options also, look at the top of this file(configuration.go)
//
// Configuration is also implements the OptionSet so it's a valid option itself, this is brilliant enough
@ -57,7 +55,7 @@ type Configuration struct {
// Note: this is the main's server Host, you can setup unlimited number of net/http servers
// listening to the $instance.Handler after the manually-called $instance.Build
//
// Default comes from iris.Listen/.Serve with iris' listeners (iris.TCP4/UNIX/TLS/LETSENCRYPT)
// Default comes from iris.Default.Listen/.Serve with iris' listeners (iris.TCP4/UNIX/TLS/LETSENCRYPT)
VHost string
// VScheme is the scheme (http:// or https://) putted at the template function '{{url }}'
@ -66,7 +64,7 @@ type Configuration struct {
// 1. You didn't start the main server using $instance.Listen/ListenTLS/ListenLETSENCRYPT or $instance.Serve($instance.TCP4()/.TLS...)
// 2. if you're using something like nginx and have iris listening with addr only(http://) but the nginx mapper is listening to https://
//
// Default comes from iris.Listen/.Serve with iris' listeners (TCP4,UNIX,TLS,LETSENCRYPT)
// Default comes from iris.Default.Listen/.Serve with iris' listeners (TCP4,UNIX,TLS,LETSENCRYPT)
VScheme string
ReadTimeout time.Duration // maximum duration before timing out read of the request
@ -105,8 +103,8 @@ type Configuration struct {
// 3. If you as developer edited the $GOPATH/src/github/kataras or any other Iris' Go dependencies at the past
// then the update process will fail.
//
// Usage: iris.Set(iris.OptionCheckForUpdates(true)) or
// iris.Config.CheckForUpdates = true or
// Usage: iris.Default.Set(iris.OptionCheckForUpdates(true)) or
// iris.Default.Config.CheckForUpdates = true or
// app := iris.New(iris.OptionCheckForUpdates(true))
// Default is false
CheckForUpdates bool
@ -159,24 +157,10 @@ type Configuration struct {
// The body will not be changed and existing data before the context.UnmarshalBody/ReadJSON/ReadXML will be not consumed.
DisableBodyConsumptionOnUnmarshal bool
// LoggerOut is the destination for output
//
// Default is os.Stdout
LoggerOut io.Writer
// LoggerPreffix is the logger's prefix to write at beginning of each line
//
// Default is [IRIS]
LoggerPreffix string
// DisableTemplateEngines set to true to disable loading the default template engine (html/template) and disallow the use of iris.UseEngine
// DisableTemplateEngines set to true to disable loading the default template engine (html/template) and disallow the use of iris.Default.UseEngine
// Defaults to false
DisableTemplateEngines bool
// IsDevelopment iris will act like a developer, for example
// If true then re-builds the templates on each request
// Defaults to false
IsDevelopment bool
// TimeFormat time format for any kind of datetime parsing
TimeFormat string
@ -204,6 +188,7 @@ type Configuration struct {
// Set implements the OptionSetter
func (c Configuration) Set(main *Configuration) {
// ignore error
mergo.MergeWithOverwrite(main, c)
}
@ -223,7 +208,7 @@ var (
// Note: this is the main's server Host, you can setup unlimited number of net/http servers
// listening to the $instance.Handler after the manually-called $instance.Build
//
// Default comes from iris.Listen/.Serve with iris' listeners (iris.TCP4/UNIX/TLS/LETSENCRYPT)
// Default comes from iris.Default.Listen/.Serve with iris' listeners (iris.TCP4/UNIX/TLS/LETSENCRYPT)
OptionVHost = func(val string) OptionSet {
return func(c *Configuration) {
c.VHost = val
@ -236,7 +221,7 @@ var (
// 1. You didn't start the main server using $instance.Listen/ListenTLS/ListenLETSENCRYPT or $instance.Serve($instance.TCP4()/.TLS...)
// 2. if you're using something like nginx and have iris listening with addr only(http://) but the nginx mapper is listening to https://
//
// Default comes from iris.Listen/.Serve with iris' listeners (TCP4,UNIX,TLS,LETSENCRYPT)
// Default comes from iris.Default.Listen/.Serve with iris' listeners (TCP4,UNIX,TLS,LETSENCRYPT)
OptionVScheme = func(val string) OptionSet {
return func(c *Configuration) {
c.VScheme = val
@ -299,8 +284,8 @@ var (
// 3. If you as developer edited the $GOPATH/src/github/kataras or any other Iris' Go dependencies at the past
// then the update process will fail.
//
// Usage: iris.Set(iris.OptionCheckForUpdates(true)) or
// iris.Config.CheckForUpdates = true or
// Usage: iris.Default.Set(iris.OptionCheckForUpdates(true)) or
// iris.Default.Config.CheckForUpdates = true or
// app := iris.New(iris.OptionCheckForUpdates(true))
// Default is false
OptionCheckForUpdates = func(val bool) OptionSet {
@ -371,41 +356,6 @@ var (
}
}
// OptionLoggerOut is the destination for output
//
// Default is os.Stdout
OptionLoggerOut = func(val io.Writer) OptionSet {
return func(c *Configuration) {
c.LoggerOut = val
}
}
// OptionLoggerPreffix is the logger's prefix to write at beginning of each line
//
// Default is [IRIS]
OptionLoggerPreffix = func(val string) OptionSet {
return func(c *Configuration) {
c.LoggerPreffix = val
}
}
// OptionDisableTemplateEngines set to true to disable loading the default template engine (html/template) and disallow the use of iris.UseEngine
// Default is false
OptionDisableTemplateEngines = func(val bool) OptionSet {
return func(c *Configuration) {
c.DisableTemplateEngines = val
}
}
// OptionIsDevelopment iris will act like a developer, for example
// If true then re-builds the templates on each request
// Default is false
OptionIsDevelopment = func(val bool) OptionSet {
return func(c *Configuration) {
c.IsDevelopment = val
}
}
// OptionTimeFormat time format for any kind of datetime parsing
OptionTimeFormat = func(val string) OptionSet {
return func(c *Configuration) {
@ -459,7 +409,6 @@ const (
DefaultDisablePathCorrection = false
DefaultEnablePathEscape = false
DefaultCharset = "UTF-8"
DefaultLoggerPreffix = "[IRIS] "
// Per-connection buffer size for requests' reading.
// This also limits the maximum header size.
//
@ -475,11 +424,6 @@ const (
DefaultWriteTimeout = 0
)
var (
// DefaultLoggerOut is the default logger's output
DefaultLoggerOut = os.Stdout
)
// DefaultConfiguration returns the default configuration for an Iris station, fills the main Configuration
func DefaultConfiguration() Configuration {
return Configuration{
@ -495,10 +439,6 @@ func DefaultConfiguration() Configuration {
FireMethodNotAllowed: false,
DisableBanner: false,
DisableBodyConsumptionOnUnmarshal: false,
LoggerOut: DefaultLoggerOut,
LoggerPreffix: DefaultLoggerPreffix,
DisableTemplateEngines: false,
IsDevelopment: false,
TimeFormat: DefaultTimeFormat,
Charset: DefaultCharset,
Gzip: false,
@ -623,7 +563,7 @@ type WebsocketConfiguration struct {
// must match the host of the request.
//
// The default behavior is to allow all origins
// you can change this behavior by setting the iris.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin
// you can change this behavior by setting the iris.Default.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin
CheckOrigin func(r *http.Request) bool
// IDGenerator used to create (and later on, set)
// an ID for each incoming websocket connections (clients).
@ -733,7 +673,7 @@ var (
ctx.EmitError(status)
}
// DefaultWebsocketCheckOrigin is the default method to allow websocket clients to connect to this server
// you can change this behavior by setting the iris.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin
// you can change this behavior by setting the iris.Default.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin
DefaultWebsocketCheckOrigin = func(r *http.Request) bool {
return true
}

View File

@ -1,88 +0,0 @@
// Black-box Testing
package iris_test
import (
"reflect"
"testing"
"github.com/kataras/iris"
)
// go test -v -run TestConfig*
func TestConfigStatic(t *testing.T) {
def := iris.DefaultConfiguration()
api := iris.New(def)
afterNew := *api.Config
if !reflect.DeepEqual(def, afterNew) {
t.Fatalf("Default configuration is not the same after NewFromConfig expected:\n %#v \ngot:\n %#v", def, afterNew)
}
afterNew.Charset = "changed"
if reflect.DeepEqual(def, afterNew) {
t.Fatalf("Configuration should be not equal, got: %#v", afterNew)
}
api = iris.New(iris.Configuration{IsDevelopment: true})
afterNew = *api.Config
if api.Config.IsDevelopment == false {
t.Fatalf("Passing a Configuration field as Option fails, expected IsDevelopment to be true but was false")
}
api = iris.New() // empty , means defaults so
if !reflect.DeepEqual(def, *api.Config) {
t.Fatalf("Default configuration is not the same after NewFromConfig expected:\n %#v \ngot:\n %#v", def, *api.Config)
}
}
func TestConfigOptions(t *testing.T) {
charset := "MYCHARSET"
dev := true
api := iris.New(iris.OptionCharset(charset), iris.OptionIsDevelopment(dev))
if got := api.Config.Charset; got != charset {
t.Fatalf("Expected configuration Charset to be: %s but got: %s", charset, got)
}
if got := api.Config.IsDevelopment; got != dev {
t.Fatalf("Expected configuration IsDevelopment to be: %#v but got: %#v", dev, got)
}
// now check if other default values are setted (should be setted automatically)
expected := iris.DefaultConfiguration()
expected.Charset = charset
expected.IsDevelopment = dev
has := *api.Config
if !reflect.DeepEqual(has, expected) {
t.Fatalf("Default configuration is not the same after New expected:\n %#v \ngot:\n %#v", expected, has)
}
}
func TestConfigOptionsDeep(t *testing.T) {
cookiename := "MYSESSIONID"
charset := "MYCHARSET"
dev := true
vhost := "mydomain.com"
// first session, after charset,dev and profilepath, no canonical order.
api := iris.New(iris.OptionSessionsCookie(cookiename), iris.OptionCharset(charset), iris.OptionIsDevelopment(dev), iris.OptionVHost(vhost))
expected := iris.DefaultConfiguration()
expected.Sessions.Cookie = cookiename
expected.Charset = charset
expected.IsDevelopment = dev
expected.VHost = vhost
has := *api.Config
if !reflect.DeepEqual(has, expected) {
t.Fatalf("DEEP configuration is not the same after New expected:\n %#v \ngot:\n %#v", expected, has)
}
}

View File

@ -17,12 +17,14 @@ import (
"runtime"
"strconv"
"strings"
"sync"
"time"
"github.com/iris-contrib/formBinder"
"github.com/kataras/go-errors"
"github.com/kataras/go-fs"
"github.com/kataras/go-sessions"
"github.com/kataras/go-template"
)
const (
@ -119,6 +121,85 @@ func (r *requestValues) Reset() {
*r = (*r)[:0]
}
type (
// ContextPool is a set of temporary *Context that may be individually saved and
// retrieved.
//
// Any item stored in the Pool may be removed automatically at any time without
// notification. If the Pool holds the only reference when this happens, the
// item might be deallocated.
//
// The ContextPool is safe for use by multiple goroutines simultaneously.
//
// ContextPool's purpose is to cache allocated but unused Contexts for later reuse,
// relieving pressure on the garbage collector.
ContextPool interface {
// Acquire returns a Context from pool.
// See Release.
Acquire(w http.ResponseWriter, r *http.Request) *Context
// Release puts a Context back to its pull, this function releases its resources.
// See Acquire.
Release(ctx *Context)
// Framework is never used, except when you're in a place where you don't have access to the *iris.Framework station
// but you need to fire a func or check its Config.
//
// Used mostly inside external routers to take the .Config.VHost
// without the need of other param receivers and refactors when changes
//
// note: we could make a variable inside contextPool which would be received by newContextPool
// but really doesn't need, we just need to borrow a context: we are in pre-build state
// so the server is not actually running yet, no runtime performance cost.
Framework() *Framework
// Run is a combination of Acquire and Release , between these two the `runner` runs,
// when `runner` finishes its job then the Context is being released.
Run(w http.ResponseWriter, r *http.Request, runner func(ctx *Context))
}
contextPool struct {
pool sync.Pool
}
)
var _ ContextPool = &contextPool{}
func (c *contextPool) Acquire(w http.ResponseWriter, r *http.Request) *Context {
ctx := c.pool.Get().(*Context)
ctx.ResponseWriter = acquireResponseWriter(w)
ctx.Request = r
return ctx
}
func (c *contextPool) Release(ctx *Context) {
// flush the body (on recorder) or just the status code (on basic response writer)
// when all finished
ctx.ResponseWriter.flushResponse()
ctx.Middleware = nil
ctx.session = nil
ctx.Request = nil
///TODO:
ctx.ResponseWriter.releaseMe()
ctx.values.Reset()
c.pool.Put(ctx)
}
func (c *contextPool) Framework() *Framework {
ctx := c.pool.Get().(*Context)
s := ctx.framework
c.pool.Put(ctx)
return s
}
func (c *contextPool) Run(w http.ResponseWriter, r *http.Request, runner func(*Context)) {
ctx := c.Acquire(w, r)
runner(ctx)
c.Release(ctx)
}
type (
// Map is just a conversion for a map[string]interface{}
@ -198,17 +279,17 @@ func (ctx *Context) GetHandlerName() string {
// it can validate paths, has sessions, path parameters and all.
//
// You can find the Route by iris.Lookup("theRouteName")
// you can set a route name as: myRoute := iris.Get("/mypath", handler)("theRouteName")
// you can set a route name as: myRoute := iris.Default.Get("/mypath", handler)("theRouteName")
// that will set a name to the route and returns its iris.Route instance for further usage.
//
// It doesn't changes the global state, if a route was "offline" it remains offline.
//
// see ExecRouteAgainst(routeName, againstRequestPath string),
// iris.None(...) and iris.SetRouteOnline/SetRouteOffline
// iris.Default.None(...) and iris.Default.SetRouteOnline/SetRouteOffline
// For more details look: https://github.com/kataras/iris/issues/585
//
// Example: https://github.com/iris-contrib/examples/tree/master/route_state
func (ctx *Context) ExecRoute(r Route) *Context {
func (ctx *Context) ExecRoute(r RouteInfo) *Context {
return ctx.ExecRouteAgainst(r, ctx.Path())
}
@ -219,23 +300,27 @@ func (ctx *Context) ExecRoute(r Route) *Context {
// it can validate paths, has sessions, path parameters and all.
//
// You can find the Route by iris.Lookup("theRouteName")
// you can set a route name as: myRoute := iris.Get("/mypath", handler)("theRouteName")
// you can set a route name as: myRoute := iris.Default.Get("/mypath", handler)("theRouteName")
// that will set a name to the route and returns its iris.Route instance for further usage.
//
// It doesn't changes the global state, if a route was "offline" it remains offline.
//
// see ExecRoute(routeName),
// iris.None(...) and iris.SetRouteOnline/SetRouteOffline
// iris.Default.None(...) and iris.Default.SetRouteOnline/SetRouteOffline
// For more details look: https://github.com/kataras/iris/issues/585
//
// Example: https://github.com/iris-contrib/examples/tree/master/route_state
func (ctx *Context) ExecRouteAgainst(r Route, againstRequestPath string) *Context {
func (ctx *Context) ExecRouteAgainst(r RouteInfo, againstRequestPath string) *Context {
if r != nil {
context := &(*ctx)
context.Middleware = context.Middleware[0:0]
context.values.Reset()
tree := ctx.framework.muxAPI.mux.getTree(r.Method(), r.Subdomain())
tree.entry.get(againstRequestPath, context)
context.Request.RequestURI = againstRequestPath
context.Request.URL.Path = againstRequestPath
context.Request.URL.RawPath = againstRequestPath
ctx.framework.policies.RouterReversionPolicy.RouteContextLinker(r, context)
// tree := ctx.framework.muxAPI.mux.getTree(r.Method(), r.Subdomain())
// tree.entry.get(againstRequestPath, context)
if len(context.Middleware) > 0 {
context.Do()
return context
@ -251,16 +336,17 @@ func (ctx *Context) ExecRouteAgainst(r Route, againstRequestPath string) *Contex
// then use the: if c := ExecRoute(r); c == nil { /* move to the next, the route is not valid */ }
//
// You can find the Route by iris.Lookup("theRouteName")
// you can set a route name as: myRoute := iris.Get("/mypath", handler)("theRouteName")
// you can set a route name as: myRoute := iris.Default.Get("/mypath", handler)("theRouteName")
// that will set a name to the route and returns its iris.Route instance for further usage.
//
// if the route found then it executes that and don't continue to the next handler
// if not found then continue to the next handler
func Prioritize(r Route) HandlerFunc {
func Prioritize(r RouteInfo) HandlerFunc {
if r != nil {
return func(ctx *Context) {
reqPath := ctx.Path()
if strings.HasPrefix(reqPath, r.StaticPath()) {
staticPath := ctx.framework.policies.RouterReversionPolicy.StaticPath(r.Path())
if strings.HasPrefix(reqPath, staticPath) {
newctx := ctx.ExecRouteAgainst(r, reqPath)
if newctx == nil { // route not found.
ctx.EmitError(StatusNotFound)
@ -317,7 +403,7 @@ func (ctx *Context) Subdomain() (subdomain string) {
func (ctx *Context) VirtualHostname() string {
realhost := ctx.Host()
hostname := realhost
virtualhost := ctx.framework.mux.hostname
virtualhost := ctx.framework.Config.VHost
if portIdx := strings.IndexByte(hostname, ':'); portIdx > 0 {
hostname = hostname[0:portIdx]
@ -496,7 +582,7 @@ var (
// -------------------------------------------------------------------------------------
// NOTE: No default max body size http package has some built'n protection for DoS attacks
// See iris.Config.MaxBytesReader, https://github.com/golang/go/issues/2093#issuecomment-66057813
// See iris.Default.Config.MaxBytesReader, https://github.com/golang/go/issues/2093#issuecomment-66057813
// and https://github.com/golang/go/issues/2093#issuecomment-66057824
// LimitRequestBodySize is a middleware which sets a request body size limit for all next handlers
@ -555,7 +641,7 @@ func (u UnmarshalerFunc) Unmarshal(data []byte, v interface{}) error {
// Examples of usage: context.ReadJSON, context.ReadXML
func (ctx *Context) UnmarshalBody(v interface{}, unmarshaler Unmarshaler) error {
if ctx.Request.Body == nil {
return errors.New("Empty body, please send request body!")
return errors.New("unmarshal: empty body")
}
rawData, err := ioutil.ReadAll(ctx.Request.Body)
@ -635,14 +721,12 @@ func (ctx *Context) Redirect(urlToRedirect string, statusHeader ...int) {
ctx.StopExecution()
httpStatus := StatusFound // a 'temporary-redirect-like' which works better than for our purpose
if statusHeader != nil && len(statusHeader) > 0 && statusHeader[0] > 0 {
if len(statusHeader) > 0 && statusHeader[0] > 0 {
httpStatus = statusHeader[0]
}
if urlToRedirect == ctx.Path() {
if ctx.framework.Config.IsDevelopment {
ctx.Log("Trying to redirect to itself. FROM: %s TO: %s", ctx.Path(), urlToRedirect)
}
ctx.Log(DevMode, "trying to redirect to itself. FROM: %s TO: %s", ctx.Path(), urlToRedirect)
}
http.Redirect(ctx.ResponseWriter, ctx.Request, urlToRedirect, httpStatus)
}
@ -739,46 +823,68 @@ func (ctx *Context) TryWriteGzip(b []byte) (int, error) {
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// renderSerialized renders contents with a serializer with status OK which you can change using RenderWithStatus or ctx.SetStatusCode(iris.StatusCode)
func (ctx *Context) renderSerialized(contentType string, obj interface{}, options ...map[string]interface{}) error {
s := ctx.framework.serializers
finalResult, err := s.Serialize(contentType, obj, options...)
if err != nil {
return err
const (
// NoLayout to disable layout for a particular template file
NoLayout = template.NoLayout
// TemplateLayoutContextKey is the name of the user values which can be used to set a template layout from a middleware and override the parent's
TemplateLayoutContextKey = "templateLayout"
)
// getGzipOption receives a default value and the render options map and returns if gzip is enabled for this render action
func getGzipOption(defaultValue bool, options map[string]interface{}) bool {
gzipOpt := options["gzip"] // we only need that, so don't create new map to keep the options.
if b, isBool := gzipOpt.(bool); isBool {
return b
}
return defaultValue
}
// gtCharsetOption receives a default value and the render options map and returns the correct charset for this render action
func getCharsetOption(defaultValue string, options map[string]interface{}) string {
charsetOpt := options["charset"]
if s, isString := charsetOpt.(string); isString {
return s
}
return defaultValue
}
func (ctx *Context) fastRenderWithStatus(status int, cType string, data []byte) (err error) {
if _, shouldFirstStatusCode := ctx.ResponseWriter.(*responseWriter); shouldFirstStatusCode {
ctx.SetStatusCode(status)
}
gzipEnabled := ctx.framework.Config.Gzip
charset := ctx.framework.Config.Charset
if len(options) > 0 {
gzipEnabled = getGzipOption(gzipEnabled, options[0]) // located to the template.go below the RenderOptions
charset = getCharsetOption(charset, options[0])
}
ctype := contentType
if ctype == contentMarkdown { // remember the text/markdown is just a custom internal iris content type, which in reallity renders html
ctype = contentHTML
if cType != contentBinary {
cType += "; charset=" + charset
}
if ctype != contentBinary { // set the charset only on non-binary data
ctype += "; charset=" + charset
}
ctx.SetContentType(ctype)
if gzipEnabled {
ctx.TryWriteGzip(finalResult)
// add the content type to the response
ctx.SetContentType(cType)
var out io.Writer
if gzipEnabled && ctx.clientAllowsGzip() {
ctx.ResponseWriter.Header().Add(varyHeader, acceptEncodingHeader)
ctx.SetHeader(contentEncodingHeader, "gzip")
gzipWriter := fs.AcquireGzipWriter(ctx.ResponseWriter)
defer fs.ReleaseGzipWriter(gzipWriter)
out = gzipWriter
} else {
ctx.ResponseWriter.Write(finalResult)
}
ctx.SetStatusCode(StatusOK)
return nil
}
// RenderTemplateSource serves a template source(raw string contents) from the first template engines which supports raw parsing returns its result as string
func (ctx *Context) RenderTemplateSource(status int, src string, binding interface{}, options ...map[string]interface{}) error {
err := ctx.framework.templates.renderSource(ctx, src, binding, options...)
if err == nil {
ctx.SetStatusCode(status)
out = ctx.ResponseWriter
}
return err
// no need to loop through the RenderPolicy, these types must be fast as possible
// with the features like gzip and custom charset too.
_, err = out.Write(data)
// we don't care for the last one it will not be written more than one if we have the *responseWriter
///TODO:
// if we have ResponseRecorder order doesn't matters but I think the transactions have bugs,
// temporary let's keep it here because it 'fixes' one of them...
ctx.SetStatusCode(status)
return
}
// RenderWithStatus builds up the response from the specified template or a serialize engine.
@ -789,11 +895,55 @@ func (ctx *Context) RenderWithStatus(status int, name string, binding interface{
ctx.SetStatusCode(status)
}
if strings.IndexByte(name, '.') > -1 { //we have template
err = ctx.framework.templates.renderFile(ctx, name, binding, options...)
} else {
err = ctx.renderSerialized(name, binding, options...)
// we do all these because we don't want to initialize a new map for each execution...
gzipEnabled := ctx.framework.Config.Gzip
charset := ctx.framework.Config.Charset
if len(options) > 0 {
gzipEnabled = getGzipOption(gzipEnabled, options[0])
charset = getCharsetOption(charset, options[0])
}
ctxLayout := ctx.GetString(TemplateLayoutContextKey)
if ctxLayout != "" {
if len(options) > 0 {
options[0]["layout"] = ctxLayout
} else {
options = []map[string]interface{}{{"layout": ctxLayout}}
}
}
// Find Content type
// if it the name is not a template file, then take that as the content type.
cType := contentHTML
if !strings.Contains(name, ".") {
// remember the text/markdown is just a custom internal
// iris content type, which in reallity renders html
if name != contentMarkdown {
cType = name
}
}
if cType != contentBinary {
cType += "; charset=" + charset
}
// add the content type to the response
ctx.SetContentType(cType)
var out io.Writer
if gzipEnabled && ctx.clientAllowsGzip() {
ctx.ResponseWriter.Header().Add(varyHeader, acceptEncodingHeader)
ctx.SetHeader(contentEncodingHeader, "gzip")
gzipWriter := fs.AcquireGzipWriter(ctx.ResponseWriter)
defer fs.ReleaseGzipWriter(gzipWriter)
out = gzipWriter
} else {
out = ctx.ResponseWriter
}
err = ctx.framework.Render(out, name, binding, options...)
// we don't care for the last one it will not be written more than one if we have the *responseWriter
///TODO:
// if we have ResponseRecorder order doesn't matters but I think the transactions have bugs , for now let's keep it here because it 'fixes' one of them...
@ -817,32 +967,45 @@ func (ctx *Context) Render(name string, binding interface{}, options ...map[stri
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engine
func (ctx *Context) MustRender(name string, binding interface{}, options ...map[string]interface{}) {
if err := ctx.Render(name, binding, options...); err != nil {
ctx.HTML(StatusServiceUnavailable, fmt.Sprintf("<h2>Template: %s</h2><b>%s</b>", name, err.Error()))
if ctx.framework.Config.IsDevelopment {
ctx.framework.Logger.Printf("MustRender panics on template: %s.Trace: %s\n", name, err)
htmlErr := ctx.HTML(StatusServiceUnavailable,
fmt.Sprintf("<h2>Template: %s</h2><b>%s</b>", name, err.Error()))
ctx.Log(DevMode, "MustRender failed to render '%s', trace: %s\n",
name, err)
if htmlErr != nil {
ctx.Log(DevMode, "MustRender also failed to render the html fallback: %s",
htmlErr.Error())
}
}
}
// 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{}, options ...map[string]interface{}) string {
return ctx.framework.TemplateString(name, binding, options...)
}
// HTML writes html string with a http status
func (ctx *Context) HTML(status int, htmlContents string) {
if err := ctx.RenderWithStatus(status, contentHTML, htmlContents); err != nil {
// if no serialize engine found for text/html
ctx.SetContentType(contentHTML + "; charset=" + ctx.framework.Config.Charset)
ctx.SetStatusCode(status)
ctx.WriteString(htmlContents)
}
}
// Data writes out the raw bytes as binary data.
func (ctx *Context) Data(status int, v []byte) error {
return ctx.RenderWithStatus(status, contentBinary, v)
//
// RenderPolicy does NOT apply to context.HTML, context.Text and context.Data
// To change their default behavior users should use
// the context.RenderWithStatus(statusCode, contentType, content, options...) instead.
func (ctx *Context) Data(status int, data []byte) error {
return ctx.fastRenderWithStatus(status, contentBinary, data)
}
// Text writes out a string as plain text.
//
// RenderPolicy does NOT apply to context.HTML, context.Text and context.Data
// To change their default behavior users should use
// the context.RenderWithStatus(statusCode, contentType, content, options...) instead.
func (ctx *Context) Text(status int, text string) error {
return ctx.fastRenderWithStatus(status, contentBinary, []byte(text))
}
// HTML writes html string with a http status
//
// RenderPolicy does NOT apply to context.HTML, context.Text and context.Data
// To change their default behavior users should use
// the context.RenderWithStatus(statusCode, contentType, content, options...) instead.
func (ctx *Context) HTML(status int, htmlContents string) error {
return ctx.fastRenderWithStatus(status, contentHTML, []byte(htmlContents))
}
// JSON marshals the given interface object and writes the JSON response.
@ -855,11 +1018,6 @@ func (ctx *Context) JSONP(status int, callback string, v interface{}) error {
return ctx.RenderWithStatus(status, contentJSONP, v, map[string]interface{}{"callback": callback})
}
// Text writes out a string as plain text.
func (ctx *Context) Text(status int, v string) error {
return ctx.RenderWithStatus(status, contentText, v)
}
// XML marshals the given interface object and writes the XML response.
func (ctx *Context) XML(status int, v interface{}) error {
return ctx.RenderWithStatus(status, contentXML, v)
@ -867,15 +1025,20 @@ func (ctx *Context) XML(status int, v interface{}) error {
// MarkdownString parses the (dynamic) markdown string and returns the converted html string
func (ctx *Context) MarkdownString(markdownText string) string {
return ctx.framework.SerializeToString(contentMarkdown, markdownText)
out := &bytes.Buffer{}
_, ok := ctx.framework.policies.RenderPolicy(out, contentMarkdown, markdownText)
if ok {
return out.String()
}
return ""
}
// 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))
func (ctx *Context) Markdown(status int, markdown string) error {
return ctx.HTML(status, ctx.MarkdownString(markdown))
}
// -------------------------------------------------------------------------------------
@ -898,9 +1061,9 @@ func (ctx *Context) staticCachePassed(modtime time.Time) bool {
// SetClientCachedBody like SetBody but it sends with an expiration datetime
// which is managed by the client-side (all major browsers supports this feature)
func (ctx *Context) SetClientCachedBody(status int, bodyContent []byte, cType string, modtime time.Time) {
func (ctx *Context) SetClientCachedBody(status int, bodyContent []byte, cType string, modtime time.Time) error {
if ctx.staticCachePassed(modtime) {
return
return nil
}
modtimeFormatted := modtime.UTC().Format(ctx.framework.Config.TimeFormat)
@ -909,7 +1072,8 @@ func (ctx *Context) SetClientCachedBody(status int, bodyContent []byte, cType st
ctx.ResponseWriter.Header().Set(lastModified, modtimeFormatted)
ctx.SetStatusCode(status)
ctx.ResponseWriter.Write(bodyContent)
_, err := ctx.ResponseWriter.Write(bodyContent)
return err
}
// ServeContent serves content, headers are autoset
@ -973,9 +1137,9 @@ func (ctx *Context) ServeFile(filename string, gzipCompression bool) error {
// SendFile sends file for force-download to the client
//
// Use this instead of ServeFile to 'force-download' bigger files to the client
func (ctx *Context) SendFile(filename string, destinationName string) {
func (ctx *Context) SendFile(filename string, destinationName string) error {
ctx.ResponseWriter.Header().Set(contentDisposition, "attachment;filename="+destinationName)
ctx.ServeFile(filename, false)
return ctx.ServeFile(filename, false)
}
// StreamWriter registers the given stream writer for populating
@ -1134,12 +1298,11 @@ func (ctx *Context) ParamInt64(key string) (int64, error) {
// hasthe form of key1=value1,key2=value2...
func (ctx *Context) ParamsSentence() string {
var buff bytes.Buffer
ctx.VisitValues(func(kb string, vg interface{}) {
v, ok := vg.(string)
ctx.VisitValues(func(k string, vi interface{}) {
v, ok := vi.(string)
if !ok {
return
}
k := string(kb)
buff.WriteString(k)
buff.WriteString("=")
buff.WriteString(v)
@ -1444,9 +1607,7 @@ func (ctx *Context) BeginTransaction(pipe func(transaction *Transaction)) {
t := newTransaction(ctx)
defer func() {
if err := recover(); err != nil {
if ctx.framework.Config.IsDevelopment {
ctx.Log(errTransactionInterrupted.Format(err).Error())
}
ctx.Log(DevMode, errTransactionInterrupted.Format(err).Error())
// complete (again or not , doesn't matters) the scope without loud
t.Complete(nil)
// we continue as normal, no need to return here*
@ -1465,8 +1626,8 @@ func (ctx *Context) BeginTransaction(pipe func(transaction *Transaction)) {
}
// Log logs to the iris defined logger
func (ctx *Context) Log(format string, a ...interface{}) {
ctx.framework.Logger.Printf(format, a...)
func (ctx *Context) Log(mode LogMode, format string, a ...interface{}) {
ctx.framework.Log(mode, fmt.Sprintf(format, a...))
}
// Framework returns the Iris instance, containing the configuration and all other fields

View File

@ -1,827 +0,0 @@
// Black-box Testing
package iris_test
import (
"encoding/json"
"encoding/xml"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"testing"
"time"
"github.com/gavv/httpexpect"
"github.com/kataras/iris"
"github.com/kataras/iris/httptest"
)
// White-box testing *
func TestContextDoNextStop(t *testing.T) {
var context iris.Context
ok := false
afterStop := false
context.Middleware = iris.Middleware{iris.HandlerFunc(func(*iris.Context) {
ok = true
}), iris.HandlerFunc(func(*iris.Context) {
ok = true
}), iris.HandlerFunc(func(*iris.Context) {
// this will never execute
afterStop = true
})}
context.Do()
if context.Pos != 0 {
t.Fatalf("Expecting position 0 for context's middleware but we got: %d", context.Pos)
}
if !ok {
t.Fatalf("Unexpected behavior, first context's middleware didn't executed")
}
ok = false
context.Next()
if int(context.Pos) != 1 {
t.Fatalf("Expecting to have position %d but we got: %d", 1, context.Pos)
}
if !ok {
t.Fatalf("Next context's middleware didn't executed")
}
context.StopExecution()
if context.Pos != 255 {
t.Fatalf("Context's StopExecution didn't worked, we expected to have position %d but we got %d", 255, context.Pos)
}
if !context.IsStopped() {
t.Fatalf("Should be stopped")
}
context.Next()
if afterStop {
t.Fatalf("We stopped the execution but the next handler was executed")
}
}
type pathParameter struct {
Key string
Value string
}
type pathParameters []pathParameter
// White-box testing *
func TestContextParams(t *testing.T) {
context := &iris.Context{}
params := pathParameters{
pathParameter{Key: "testkey", Value: "testvalue"},
pathParameter{Key: "testkey2", Value: "testvalue2"},
pathParameter{Key: "id", Value: "3"},
pathParameter{Key: "bigint", Value: "548921854390354"},
}
for _, p := range params {
context.Set(p.Key, p.Value)
}
if v := context.Param(params[0].Key); v != params[0].Value {
t.Fatalf("Expecting parameter value to be %s but we got %s", params[0].Value, context.Param("testkey"))
}
if v := context.Param(params[1].Key); v != params[1].Value {
t.Fatalf("Expecting parameter value to be %s but we got %s", params[1].Value, context.Param("testkey2"))
}
if context.ParamsLen() != len(params) {
t.Fatalf("Expecting to have %d parameters but we got %d", len(params), context.ParamsLen())
}
if vi, err := context.ParamInt(params[2].Key); err != nil {
t.Fatalf("Unexpecting error on context's ParamInt while trying to get the integer of the %s", params[2].Value)
} else if vi != 3 {
t.Fatalf("Expecting to receive %d but we got %d", 3, vi)
}
if vi, err := context.ParamInt64(params[3].Key); err != nil {
t.Fatalf("Unexpecting error on context's ParamInt while trying to get the integer of the %s", params[2].Value)
} else if vi != 548921854390354 {
t.Fatalf("Expecting to receive %d but we got %d", 548921854390354, vi)
}
// end-to-end test now, note that we will not test the whole mux here, this happens on http_test.go
iris.ResetDefault()
expectedParamsStr := "param1=myparam1,param2=myparam2,param3=myparam3afterstatic,anything=/andhere/anything/you/like"
iris.Get("/path/:param1/:param2/staticpath/:param3/*anything", func(ctx *iris.Context) {
paramsStr := ctx.ParamsSentence()
ctx.WriteString(paramsStr)
})
httptest.New(iris.Default, t).GET("/path/myparam1/myparam2/staticpath/myparam3afterstatic/andhere/anything/you/like").Expect().Status(iris.StatusOK).Body().Equal(expectedParamsStr)
}
func TestContextURLParams(t *testing.T) {
iris.ResetDefault()
passedParams := map[string]string{"param1": "value1", "param2": "value2"}
iris.Get("/", func(ctx *iris.Context) {
params := ctx.URLParams()
ctx.JSON(iris.StatusOK, params)
})
e := httptest.New(iris.Default, t, httptest.Debug(true))
e.GET("/").WithQueryObject(passedParams).Expect().Status(iris.StatusOK).JSON().Equal(passedParams)
}
// hoststring returns the full host, will return the HOST:IP
func TestContextHostString(t *testing.T) {
iris.ResetDefault()
iris.Default.Config.VHost = "0.0.0.0:8080"
iris.Get("/", func(ctx *iris.Context) {
ctx.WriteString(ctx.Host())
})
iris.Get("/wrong", func(ctx *iris.Context) {
ctx.WriteString(ctx.Host() + "w")
})
e := httptest.New(iris.Default, t)
e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(iris.Default.Config.VHost)
e.GET("/wrong").Expect().Body().NotEqual(iris.Default.Config.VHost)
}
// VirtualHostname returns the hostname only,
// if the host starts with 127.0.0.1 or localhost it gives the registered hostname part of the listening addr
func TestContextVirtualHostName(t *testing.T) {
iris.ResetDefault()
vhost := "mycustomvirtualname.com"
iris.Default.Config.VHost = vhost + ":8080"
iris.Get("/", func(ctx *iris.Context) {
ctx.WriteString(ctx.VirtualHostname())
})
iris.Get("/wrong", func(ctx *iris.Context) {
ctx.WriteString(ctx.VirtualHostname() + "w")
})
e := httptest.New(iris.Default, t)
e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(vhost)
e.GET("/wrong").Expect().Body().NotEqual(vhost)
}
func TestContextFormValueString(t *testing.T) {
iris.ResetDefault()
var k, v string
k = "postkey"
v = "postvalue"
iris.Post("/", func(ctx *iris.Context) {
ctx.WriteString(k + "=" + ctx.FormValue(k))
})
e := httptest.New(iris.Default, t)
e.POST("/").WithFormField(k, v).Expect().Status(iris.StatusOK).Body().Equal(k + "=" + v)
}
func TestContextSubdomain(t *testing.T) {
iris.ResetDefault()
iris.Default.Config.VHost = "mydomain.com:9999"
//Default.Config.Tester.ListeningAddr = "mydomain.com:9999"
// Default.Config.Tester.ExplicitURL = true
iris.Party("mysubdomain.").Get("/mypath", func(ctx *iris.Context) {
ctx.WriteString(ctx.Subdomain())
})
e := httptest.New(iris.Default, t)
e.GET("/").WithURL("http://mysubdomain.mydomain.com:9999").Expect().Status(iris.StatusNotFound)
e.GET("/mypath").WithURL("http://mysubdomain.mydomain.com:9999").Expect().Status(iris.StatusOK).Body().Equal("mysubdomain")
//e.GET("http://mysubdomain.mydomain.com:9999").Expect().Status(StatusNotFound)
//e.GET("http://mysubdomain.mydomain.com:9999/mypath").Expect().Status(iris.StatusOK).Body().Equal("mysubdomain")
}
type testBinderData struct {
Username string
Mail string
Data []string `form:"mydata" json:"mydata"`
}
type testBinderXMLData struct {
XMLName xml.Name `xml:"info"`
FirstAttr string `xml:"first,attr"`
SecondAttr string `xml:"second,attr"`
Name string `xml:"name",json:"name"`
Birth string `xml:"birth",json:"birth"`
Stars int `xml:"stars",json:"stars"`
}
type testBinder struct {
//pointer of testBinderDataJSON or testBinderXMLData
vp interface{}
m iris.Unmarshaler
shouldError bool
}
func (tj *testBinder) Decode(data []byte) error {
if tj.shouldError {
return fmt.Errorf("Should error")
}
return tj.m.Unmarshal(data, tj.vp)
}
func testUnmarshaler(t *testing.T, tb *testBinder,
write func(ctx *iris.Context)) *httpexpect.Request {
// a very dirty and awful way but here we must test in deep
// the custom object's decoder error with the custom
// unmarshaler result whenever the testUnmarshaler called.
if tb.shouldError == false {
tb.shouldError = true
testUnmarshaler(t, tb, nil)
tb.shouldError = false
}
iris.ResetDefault()
h := func(ctx *iris.Context) {
err := ctx.UnmarshalBody(tb.vp, tb.m)
if tb.shouldError && err == nil {
t.Fatalf("Should prompted for error 'Should error' but not error returned from the custom decoder!")
} else if err != nil {
t.Fatalf("Error when parsing the body: %s", err.Error())
}
if write != nil {
write(ctx)
}
if iris.Config.DisableBodyConsumptionOnUnmarshal {
rawData, _ := ioutil.ReadAll(ctx.Request.Body)
if len(rawData) == 0 {
t.Fatalf("Expected data to NOT BE consumed by the previous UnmarshalBody call but we got empty body.")
}
}
}
iris.Post("/bind_req_body", h)
e := httptest.New(iris.Default, t)
return e.POST("/bind_req_body")
}
// same as DecodeBody
// JSON, XML by DecodeBody passing the default unmarshalers
func TestContextBinders(t *testing.T) {
passed := map[string]interface{}{"Username": "myusername",
"Mail": "mymail@iris-go.com",
"mydata": []string{"mydata1", "mydata2"}}
expectedObject := testBinderData{Username: "myusername",
Mail: "mymail@iris-go.com",
Data: []string{"mydata1", "mydata2"}}
// JSON
vJSON := &testBinder{&testBinderData{},
iris.UnmarshalerFunc(json.Unmarshal), false}
testUnmarshaler(
t,
vJSON,
func(ctx *iris.Context) {
ctx.JSON(iris.StatusOK, vJSON.vp)
}).
WithJSON(passed).
Expect().
Status(iris.StatusOK).
JSON().Object().Equal(expectedObject)
// XML
expectedObj := testBinderXMLData{
XMLName: xml.Name{Local: "info", Space: "info"},
FirstAttr: "this is the first attr",
SecondAttr: "this is the second attr",
Name: "Iris web framework",
Birth: "13 March 2016",
Stars: 5758,
}
expectedAndPassedObjText := `<` + expectedObj.XMLName.Local + ` first="` +
expectedObj.FirstAttr + `" second="` +
expectedObj.SecondAttr + `"><name>` +
expectedObj.Name + `</name><birth>` +
expectedObj.Birth + `</birth><stars>` +
strconv.Itoa(expectedObj.Stars) + `</stars></info>`
vXML := &testBinder{&testBinderXMLData{},
iris.UnmarshalerFunc(xml.Unmarshal), false}
testUnmarshaler(
t,
vXML,
func(ctx *iris.Context) {
ctx.XML(iris.StatusOK, vXML.vp)
}).
WithText(expectedAndPassedObjText).
Expect().
Status(iris.StatusOK).
Body().Equal(expectedAndPassedObjText)
// JSON with DisableBodyConsumptionOnUnmarshal
iris.Config.DisableBodyConsumptionOnUnmarshal = true
testUnmarshaler(
t,
vJSON,
func(ctx *iris.Context) {
ctx.JSON(iris.StatusOK, vJSON.vp)
}).
WithJSON(passed).
Expect().
Status(iris.StatusOK).
JSON().Object().Equal(expectedObject)
}
func TestContextReadForm(t *testing.T) {
iris.ResetDefault()
iris.Post("/form", func(ctx *iris.Context) {
obj := testBinderData{}
err := ctx.ReadForm(&obj)
if err != nil {
t.Fatalf("Error when parsing the FORM: %s", err.Error())
}
ctx.JSON(iris.StatusOK, obj)
})
e := httptest.New(iris.Default, t)
passed := map[string]interface{}{"Username": "myusername", "Mail": "mymail@iris-go.com", "mydata": url.Values{"[0]": []string{"mydata1"},
"[1]": []string{"mydata2"}}}
expectedObject := testBinderData{Username: "myusername", Mail: "mymail@iris-go.com", Data: []string{"mydata1", "mydata2"}}
e.POST("/form").WithForm(passed).Expect().Status(iris.StatusOK).JSON().Object().Equal(expectedObject)
}
// TestContextRedirectTo tests the named route redirect action
func TestContextRedirectTo(t *testing.T) {
iris.ResetDefault()
h := func(ctx *iris.Context) { ctx.WriteString(ctx.Path()) }
iris.Get("/mypath", h)("my-path")
iris.Get("/mypostpath", h)("my-post-path")
iris.Get("mypath/with/params/:param1/:param2", func(ctx *iris.Context) {
if l := ctx.ParamsLen(); l != 2 {
t.Fatalf("Strange error, expecting parameters to be two but we got: %d", l)
}
ctx.WriteString(ctx.Path())
})("my-path-with-params")
iris.Get("/redirect/to/:routeName/*anyparams", func(ctx *iris.Context) {
routeName := ctx.Param("routeName")
var args []interface{}
anyparams := ctx.Param("anyparams")
if anyparams != "" && anyparams != "/" {
params := strings.Split(anyparams[1:], "/") // firstparam/secondparam
for _, s := range params {
args = append(args, s)
}
}
ctx.RedirectTo(routeName, args...)
})
e := httptest.New(iris.Default, t)
e.GET("/redirect/to/my-path/").Expect().Status(iris.StatusOK).Body().Equal("/mypath")
e.GET("/redirect/to/my-post-path/").Expect().Status(iris.StatusOK).Body().Equal("/mypostpath")
e.GET("/redirect/to/my-path-with-params/firstparam/secondparam").Expect().Status(iris.StatusOK).Body().Equal("/mypath/with/params/firstparam/secondparam")
}
func TestContextUserValues(t *testing.T) {
iris.ResetDefault()
testCustomObjUserValue := struct{ Name string }{Name: "a name"}
values := map[string]interface{}{"key1": "value1", "key2": "value2", "key3": 3, "key4": testCustomObjUserValue, "key5": map[string]string{"key": "value"}}
iris.Get("/test", func(ctx *iris.Context) {
for k, v := range values {
ctx.Set(k, v)
}
}, func(ctx *iris.Context) {
for k, v := range values {
userValue := ctx.Get(k)
if userValue != v {
t.Fatalf("Expecting user value: %s to be equal with: %#v but got: %#v", k, v, userValue)
}
if m, isMap := userValue.(map[string]string); isMap {
if m["key"] != v.(map[string]string)["key"] {
t.Fatalf("Expecting user value: %s to be equal with: %#v but got: %#v", k, v.(map[string]string)["key"], m["key"])
}
} else {
if userValue != v {
t.Fatalf("Expecting user value: %s to be equal with: %#v but got: %#v", k, v, userValue)
}
}
}
})
e := httptest.New(iris.Default, t)
e.GET("/test").Expect().Status(iris.StatusOK)
}
func TestContextCookieSetGetRemove(t *testing.T) {
iris.ResetDefault()
key := "mykey"
value := "myvalue"
iris.Get("/set", func(ctx *iris.Context) {
ctx.SetCookieKV(key, value) // should return non empty cookies
})
iris.Get("/set_advanced", func(ctx *iris.Context) {
c := &http.Cookie{}
c.Name = key
c.Value = value
c.HttpOnly = true
c.Expires = time.Now().Add(time.Duration((60 * 60 * 24 * 7 * 4)) * time.Second)
ctx.SetCookie(c)
})
iris.Get("/get", func(ctx *iris.Context) {
ctx.WriteString(ctx.GetCookie(key)) // should return my value
})
iris.Get("/remove", func(ctx *iris.Context) {
ctx.RemoveCookie(key)
cookieFound := false
ctx.VisitAllCookies(func(k, v string) {
cookieFound = true
})
if cookieFound {
t.Fatalf("Cookie has been found, when it shouldn't!")
}
ctx.WriteString(ctx.GetCookie(key)) // should return ""
})
e := httptest.New(iris.Default, t)
e.GET("/set").Expect().Status(iris.StatusOK).Cookies().NotEmpty()
e.GET("/get").Expect().Status(iris.StatusOK).Body().Equal(value)
e.GET("/remove").Expect().Status(iris.StatusOK).Body().Equal("")
// test again with advanced set
e.GET("/set_advanced").Expect().Status(iris.StatusOK).Cookies().NotEmpty()
e.GET("/get").Expect().Status(iris.StatusOK).Body().Equal(value)
e.GET("/remove").Expect().Status(iris.StatusOK).Body().Equal("")
}
func TestContextSessions(t *testing.T) {
t.Parallel()
values := map[string]interface{}{
"Name": "iris",
"Months": "4",
"Secret": "dsads£2132215£%%Ssdsa",
}
iris.ResetDefault()
iris.Default.Config.Sessions.Cookie = "mycustomsessionid"
writeValues := func(ctx *iris.Context) {
sessValues := ctx.Session().GetAll()
ctx.JSON(iris.StatusOK, sessValues)
}
if testEnableSubdomain {
iris.Party(testSubdomain+".").Get("/get", func(ctx *iris.Context) {
writeValues(ctx)
})
}
iris.Post("set", func(ctx *iris.Context) {
vals := make(map[string]interface{}, 0)
if err := ctx.ReadJSON(&vals); err != nil {
t.Fatalf("Cannot readjson. Trace %s", err.Error())
}
for k, v := range vals {
ctx.Session().Set(k, v)
}
})
iris.Get("/get", func(ctx *iris.Context) {
writeValues(ctx)
})
iris.Get("/clear", func(ctx *iris.Context) {
ctx.Session().Clear()
writeValues(ctx)
})
iris.Get("/destroy", func(ctx *iris.Context) {
ctx.SessionDestroy()
writeValues(ctx)
// the cookie and all values should be empty
})
// request cookie should be empty
iris.Get("/after_destroy", func(ctx *iris.Context) {
})
iris.Default.Config.VHost = "mydomain.com"
e := httptest.New(iris.Default, t)
e.POST("/set").WithJSON(values).Expect().Status(iris.StatusOK).Cookies().NotEmpty()
e.GET("/get").Expect().Status(iris.StatusOK).JSON().Object().Equal(values)
if testEnableSubdomain {
es := subdomainTester(e)
es.Request("GET", "/get").Expect().Status(iris.StatusOK).JSON().Object().Equal(values)
}
// test destroy which also clears first
d := e.GET("/destroy").Expect().Status(iris.StatusOK)
d.JSON().Object().Empty()
// This removed: d.Cookies().Empty(). Reason:
// httpexpect counts the cookies setted or deleted at the response time, but cookie is not removed, to be really removed needs to SetExpire(now-1second) so,
// test if the cookies removed on the next request, like the browser's behavior.
e.GET("/after_destroy").Expect().Status(iris.StatusOK).Cookies().Empty()
// set and clear again
e.POST("/set").WithJSON(values).Expect().Status(iris.StatusOK).Cookies().NotEmpty()
e.GET("/clear").Expect().Status(iris.StatusOK).JSON().Object().Empty()
}
type renderTestInformationType struct {
XMLName xml.Name `xml:"info"`
FirstAttr string `xml:"first,attr"`
SecondAttr string `xml:"second,attr"`
Name string `xml:"name",json:"name"`
Birth string `xml:"birth",json:"birth"`
Stars int `xml:"stars",json:"stars"`
}
func TestContextRenderRest(t *testing.T) {
iris.ResetDefault()
dataContents := []byte("Some binary data here.")
textContents := "Plain text here"
JSONPContents := map[string]string{"hello": "jsonp"}
JSONPCallback := "callbackName"
JSONXMLContents := renderTestInformationType{
XMLName: xml.Name{Local: "info", Space: "info"}, // only need to verify that later
FirstAttr: "this is the first attr",
SecondAttr: "this is the second attr",
Name: "Iris web framework",
Birth: "13 March 2016",
Stars: 4064,
}
markdownContents := "# Hello dynamic markdown from Iris"
iris.Get("/data", func(ctx *iris.Context) {
ctx.Data(iris.StatusOK, dataContents)
})
iris.Get("/text", func(ctx *iris.Context) {
ctx.Text(iris.StatusOK, textContents)
})
iris.Get("/jsonp", func(ctx *iris.Context) {
ctx.JSONP(iris.StatusOK, JSONPCallback, JSONPContents)
})
iris.Get("/json", func(ctx *iris.Context) {
ctx.JSON(iris.StatusOK, JSONXMLContents)
})
iris.Get("/xml", func(ctx *iris.Context) {
ctx.XML(iris.StatusOK, JSONXMLContents)
})
iris.Get("/markdown", func(ctx *iris.Context) {
ctx.Markdown(iris.StatusOK, markdownContents)
})
e := httptest.New(iris.Default, t)
dataT := e.GET("/data").Expect().Status(iris.StatusOK)
dataT.Header("Content-Type").Equal("application/octet-stream")
dataT.Body().Equal(string(dataContents))
textT := e.GET("/text").Expect().Status(iris.StatusOK)
textT.Header("Content-Type").Equal("text/plain; charset=UTF-8")
textT.Body().Equal(textContents)
JSONPT := e.GET("/jsonp").Expect().Status(iris.StatusOK)
JSONPT.Header("Content-Type").Equal("application/javascript; charset=UTF-8")
JSONPT.Body().Equal(JSONPCallback + `({"hello":"jsonp"});`)
JSONT := e.GET("/json").Expect().Status(iris.StatusOK)
JSONT.Header("Content-Type").Equal("application/json; charset=UTF-8")
JSONT.JSON().Object().Equal(JSONXMLContents)
XMLT := e.GET("/xml").Expect().Status(iris.StatusOK)
XMLT.Header("Content-Type").Equal("text/xml; charset=UTF-8")
XMLT.Body().Equal(`<` + JSONXMLContents.XMLName.Local + ` first="` + JSONXMLContents.FirstAttr + `" second="` + JSONXMLContents.SecondAttr + `"><name>` + JSONXMLContents.Name + `</name><birth>` + JSONXMLContents.Birth + `</birth><stars>` + strconv.Itoa(JSONXMLContents.Stars) + `</stars></info>`)
markdownT := e.GET("/markdown").Expect().Status(iris.StatusOK)
markdownT.Header("Content-Type").Equal("text/html; charset=UTF-8")
markdownT.Body().Equal("<h1>" + markdownContents[2:] + "</h1>\n")
}
func TestContextPreRender(t *testing.T) {
iris.ResetDefault()
preRender := func(errMsg string, shouldContinue bool) iris.PreRender {
return func(ctx *iris.Context,
src string,
binding interface{},
options ...map[string]interface{}) bool {
// put the 'Error' binding here, for the shake of the test
if b, isMap := binding.(map[string]interface{}); isMap {
msg := ""
if prevMsg := b["Error"]; prevMsg != nil {
// we have a previous message
msg += prevMsg.(string)
}
msg += errMsg
b["Error"] = msg
}
return shouldContinue
}
}
errMsg1 := "thereIsAnError"
errMsg2 := "thereIsASecondError"
errMsg3 := "thereisAThirdError"
// only errMsg1 and errMsg2 should be rendered because
// on errMsg2 we stop the execution
iris.UsePreRender(preRender(errMsg1, true))
iris.UsePreRender(preRender(errMsg2, false))
iris.UsePreRender(preRender(errMsg3, false)) // false doesn't matters here
iris.Get("/", func(ctx *iris.Context) {
ctx.RenderTemplateSource(iris.StatusOK, "<h1>HI {{.Username}}. Error: {{.Error}}</h1>", map[string]interface{}{"Username": "kataras"})
})
e := httptest.New(iris.Default, t)
expected := "<h1>HI kataras. Error: " + errMsg1 + errMsg2 + "</h1>"
e.GET("/").Expect().Status(iris.StatusOK).Body().Contains(expected)
}
func TestTemplatesDisabled(t *testing.T) {
iris.ResetDefault()
defer iris.Close()
iris.Default.Config.DisableTemplateEngines = true
file := "index.html"
errTmpl := "<h2>Template: %s</h2><b>%s</b>"
expctedErrMsg := fmt.Sprintf(errTmpl, file, "Error: Unable to execute a template. Trace: Templates are disabled '.Config.DisableTemplatesEngines = true' please turn that to false, as defaulted.\n")
iris.Get("/renderErr", func(ctx *iris.Context) {
ctx.MustRender(file, nil)
})
e := httptest.New(iris.Default, t)
e.GET("/renderErr").Expect().Status(iris.StatusServiceUnavailable).Body().Equal(expctedErrMsg)
}
func TestTransactions(t *testing.T) {
iris.ResetDefault()
firstTransactionFailureMessage := "Error: Virtual failure!!!"
secondTransactionSuccessHTMLMessage := "<h1>This will sent at all cases because it lives on different transaction and it doesn't fails</h1>"
persistMessage := "<h1>I persist show this message to the client!</h1>"
maybeFailureTransaction := func(shouldFail bool, isRequestScoped bool) func(t *iris.Transaction) {
return func(t *iris.Transaction) {
// OPTIONAl, the next transactions and the flow will not be skipped if this transaction fails
if isRequestScoped {
t.SetScope(iris.RequestTransactionScope)
}
// OPTIONAL STEP:
// create a new custom type of error here to keep track of the status code and reason message
err := iris.NewTransactionErrResult()
t.Context.Text(iris.StatusOK, "Blablabla this should not be sent to the client because we will fill the err with a message and status")
fail := shouldFail
if fail {
err.StatusCode = iris.StatusInternalServerError
err.Reason = firstTransactionFailureMessage
}
// OPTIONAl STEP:
// but useful if we want to post back an error message to the client if the transaction failed.
// if the reason is empty then the transaction completed successfully,
// otherwise we rollback the whole response body and cookies and everything lives inside the transaction.Request.
t.Complete(err)
}
}
successTransaction := func(scope *iris.Transaction) {
if scope.Context.Request.RequestURI == "/failAllBecauseOfRequestScopeAndFailure" {
t.Fatalf("We are inside successTransaction but the previous REQUEST SCOPED TRANSACTION HAS FAILED SO THiS SHOULD NOT BE RAN AT ALL")
}
scope.Context.HTML(iris.StatusOK,
secondTransactionSuccessHTMLMessage)
// * if we don't have any 'throw error' logic then no need of scope.Complete()
}
persistMessageHandler := func(ctx *iris.Context) {
// OPTIONAL, depends on the usage:
// at any case, what ever happens inside the context's transactions send this to the client
ctx.HTML(iris.StatusOK, persistMessage)
}
iris.Get("/failFirsTransactionButSuccessSecondWithPersistMessage", func(ctx *iris.Context) {
ctx.BeginTransaction(maybeFailureTransaction(true, false))
ctx.BeginTransaction(successTransaction)
persistMessageHandler(ctx)
})
iris.Get("/failFirsTransactionButSuccessSecond", func(ctx *iris.Context) {
ctx.BeginTransaction(maybeFailureTransaction(true, false))
ctx.BeginTransaction(successTransaction)
})
iris.Get("/failAllBecauseOfRequestScopeAndFailure", func(ctx *iris.Context) {
ctx.BeginTransaction(maybeFailureTransaction(true, true))
ctx.BeginTransaction(successTransaction)
})
customErrorTemplateText := "<h1>custom error</h1>"
iris.OnError(iris.StatusInternalServerError, func(ctx *iris.Context) {
ctx.Text(iris.StatusInternalServerError, customErrorTemplateText)
})
failureWithRegisteredErrorHandler := func(ctx *iris.Context) {
ctx.BeginTransaction(func(transaction *iris.Transaction) {
transaction.SetScope(iris.RequestTransactionScope)
err := iris.NewTransactionErrResult()
err.StatusCode = iris.StatusInternalServerError // set only the status code in order to execute the registered template
transaction.Complete(err)
})
ctx.Text(iris.StatusOK, "this will not be sent to the client because first is requested scope and it's failed")
}
iris.Get("/failAllBecauseFirstTransactionFailedWithRegisteredErrorTemplate", failureWithRegisteredErrorHandler)
e := httptest.New(iris.Default, t)
e.GET("/failFirsTransactionButSuccessSecondWithPersistMessage").
Expect().
Status(iris.StatusOK).
ContentType("text/html", iris.Config.Charset).
Body().
Equal(secondTransactionSuccessHTMLMessage + persistMessage)
e.GET("/failFirsTransactionButSuccessSecond").
Expect().
Status(iris.StatusOK).
ContentType("text/html", iris.Config.Charset).
Body().
Equal(secondTransactionSuccessHTMLMessage)
e.GET("/failAllBecauseOfRequestScopeAndFailure").
Expect().
Status(iris.StatusInternalServerError).
Body().
Equal(firstTransactionFailureMessage)
e.GET("/failAllBecauseFirstTransactionFailedWithRegisteredErrorTemplate").
Expect().
Status(iris.StatusInternalServerError).
Body().
Equal(customErrorTemplateText)
}
func TestLimitRequestBodySize(t *testing.T) {
const maxBodySize = 1 << 20
api := iris.New()
// or inside handler: ctx.SetMaxRequestBodySize(int64(maxBodySize))
api.Use(iris.LimitRequestBodySize(maxBodySize))
api.Post("/", func(ctx *iris.Context) {
b, err := ioutil.ReadAll(ctx.Request.Body)
if len(b) > maxBodySize {
// this is a fatal error it should never happened.
t.Fatalf("body is larger (%d) than maxBodySize (%d) even if we add the LimitRequestBodySize middleware", len(b), maxBodySize)
}
// if is larger then send a bad request status
if err != nil {
ctx.WriteHeader(iris.StatusBadRequest)
ctx.Writef(err.Error())
return
}
ctx.Write(b)
})
// UseGlobal should be called at the end used to prepend handlers
// api.UseGlobal(iris.LimitRequestBodySize(int64(maxBodySize)))
e := httptest.New(api, t)
// test with small body
e.POST("/").WithBytes([]byte("ok")).Expect().Status(iris.StatusOK).Body().Equal("ok")
// test with equal to max body size limit
bsent := make([]byte, maxBodySize, maxBodySize)
e.POST("/").WithBytes(bsent).Expect().Status(iris.StatusOK).Body().Length().Equal(len(bsent))
// test with larger body sent and wait for the custom response
largerBSent := make([]byte, maxBodySize+1, maxBodySize+1)
e.POST("/").WithBytes(largerBSent).Expect().Status(iris.StatusBadRequest).Body().Equal("http: request body too large")
}

521
doc.go Normal file
View File

@ -0,0 +1,521 @@
// Copyright (c) 2016-2017 Gerasimos Maropoulos
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
/*
Iris back-end web framework provides efficient and well-designed toolbox with robust set of features to
create your own perfect high performance web application
with unlimited portability using the Go Programming Language.
Note: This package is under active development status.
Each month a new version is releasing
to adapt the latest web trends and technologies.
Basic HTTP API.
Iris is a very pluggable ecosystem,
router can be customized by adapting a 'RouterBuilderPolicy && RouterReversionPolicy'.
By adapted a router users are able to use router's features on the route's Path,
the rest of the HTTP API and Context's calls remains the same for all routers, as expected.
- httprouter, it's a custom version of https://github.comjulienschmidt/httprouter,
which is edited to support iris' subdomains, reverse routing, custom http errors and a lot features,
it should be a bit faster than the original too because of iris' Context.
It uses `/mypath/:firstParameter/path/:secondParameter` and `/mypath/*wildcardParamName` .
- gorillamuxa, it's the https://github.com/gorilla/mux which supports subdomains,
custom http errors, reverse routing, pattern matching via regex and the rest of the iris' features.
It uses `/mypath/{firstParameter:any-regex-valid-here}/path/{secondParameter}` and `/mypath/{wildcardParamName:.*}`
Example code:
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter" // <--- or adaptors/gorillamux
)
func main(){
app := iris.New()
app.Adapt(httprouter.New()) // <--- or gorillamux.New()
// HTTP Method: GET
// PATH: http://127.0.0.1/
// Handler(s): index
app.Get("/", index)
app.Listen(":80")
}
func index(ctx *iris.Context){
ctx.HTML(iris.StatusOK, "<h1> Welcome to my page!</h1>")
}
All HTTP methods are supported, users can register handlers for same paths on different methods.
The first parameter is the HTTP Method,
second parameter is the request path of the route,
third variadic parameter should contains one or more iris.Handler/HandlerFunc executed
by the registered order when a user requests for that specific resouce path from the server.
Example code:
app := iris.New()
app.Handle("GET", "/about", aboutHandler)
type aboutHandler struct {}
func (a aboutHandler) Serve(ctx *iris.Context){
ctx.HTML("Hello from /about, executed from an iris.Handler")
}
app.HandleFunc("GET", "/contact", func(ctx *iris.Context){
ctx.HTML(iris.StatusOK, "Hello from /contact, executed from an iris.HandlerFunc")
})
In order to make things easier for the user, Iris provides functions for all HTTP Methods.
The first parameter is the request path of the route,
second variadic parameter should contains one or more iris.HandlerFunc executed
by the registered order when a user requests for that specific resouce path from the server.
Example code:
app := iris.New()
// Method: "GET"
app.Get("/", handler)
// Method: "POST"
app.Post("/", handler)
// Method: "PUT"
app.Put("/", handler)
// Method: "DELETE"
app.Delete("/", handler)
// Method: "OPTIONS"
app.Options("/", handler)
// Method: "TRACE"
app.Trace("/", handler)
// Method: "CONNECT"
app.Connect("/", handler)
// Method: "HEAD"
app.Head("/", handler)
// Method: "PATCH"
app.Patch("/", handler)
// register the route for all HTTP Methods
app.Any("/", handler)
func handler(ctx *iris.Context){
ctx.Writef("Hello from method: %s and path: %s", ctx.Method(), ctx.Path())
}
Parameterized route's Path, depends on the selected router.
Note: This is the only difference between the routers, the registered path form, the API remains the same for both.
Example `gorillamux` code:
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/gorillamux"
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(gorillamux.New())
app.OnError(iris.StatusNotFound, func(ctx *iris.Context){
ctx.HTML(iris.StatusNotFound, "<h1> custom http error page </h1>")
})
app.Get("/healthcheck", h)
gamesMiddleware := func(ctx *iris.Context) {
println(ctx.Method() + ": " + ctx.Path())
ctx.Next()
}
games:= app.Party("/games", gamesMiddleware)
{ // braces are optional of course, it's just a style of code
games.Get("/{gameID:[0-9]+}/clans", h)
games.Get("/{gameID:[0-9]+}/clans/clan/{publicID:[0-9]+}", h)
games.Get("/{gameID:[0-9]+}/clans/search", h)
games.Put("/{gameID:[0-9]+}/players/{publicID:[0-9]+}", h)
games.Put("/{gameID:[0-9]+}/clans/clan/{publicID:[0-9]+}", h)
games.Post("/{gameID:[0-9]+}/clans", h)
games.Post("/{gameID:[0-9]+}/players", h)
games.Post("/{gameID:[0-9]+}/clans/{publicID:[0-9]+}/leave", h)
games.Post("/{gameID:[0-9]+}/clans/{clanPublicID:[0-9]+}/memberships/application", h)
games.Post("/{gameID:[0-9]+}/clans/{clanPublicID:[0-9]+}/memberships/application/:action", h)
games.Post("/{gameID:[0-9]+}/clans/{clanPublicID:[0-9]+}/memberships/invitation", h)
games.Post("/{gameID:[0-9]+}/clans/{clanPublicID:[0-9]+}/memberships/invitation/:action", h)
games.Post("/{gameID:[0-9]+}/clans/{clanPublicID:[0-9]+}/memberships/delete", h)
games.Post("/{gameID:[0-9]+}/clans/{clanPublicID:[0-9]+}/memberships/promote", h)
games.Post("/{gameID:[0-9]+}/clans/{clanPublicID:[0-9]+}/memberships/demote", h)
}
app.Get("/anything/{anythingparameter:.*}", func(ctx *iris.Context){
s := ctx.Param("anythingparameter")
ctx.Writef("The path after /anything is: %s",s)
})
mysubdomain:= app.Party("mysubdomain.")
// http://mysubdomain.myhost.com/
mysudomain.Get("/", h)
app.Listen("myhost.com:80")
}
func h(ctx *iris.Context) {
ctx.HTML(iris.StatusOK, "<h1>Path<h1/>"+ctx.Path())
}
Example `httprouter` code:
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter" // <---- NEW
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New()) // <---- NEW
app.OnError(iris.StatusNotFound, func(ctx *iris.Context){
ctx.HTML(iris.StatusNotFound, "<h1> custom http error page </h1>")
})
app.Get("/healthcheck", h)
gamesMiddleware := func(ctx *iris.Context) {
println(ctx.Method() + ": " + ctx.Path())
ctx.Next()
}
games:= app.Party("/games", gamesMiddleware)
{ // braces are optional of course, it's just a style of code
games.Get("/:gameID/clans", h)
games.Get("/:gameID/clans/clan/:publicID", h)
games.Get("/:gameID/clans/search", h)
games.Put("/:gameID/players/:publicID", h)
games.Put("/:gameID/clans/clan/:publicID", h)
games.Post("/:gameID/clans", h)
games.Post("/:gameID/players", h)
games.Post("/:gameID/clans/:publicID/leave", h)
games.Post("/:gameID/clans/:clanPublicID/memberships/application", h)
games.Post("/:gameID/clans/:clanPublicID/memberships/application/:action", h)
games.Post("/:gameID/clans/:clanPublicID/memberships/invitation", h)
games.Post("/:gameID/clans/:clanPublicID/memberships/invitation/:action", h)
games.Post("/:gameID/clans/:clanPublicID/memberships/delete", h)
games.Post("/:gameID/clans/:clanPublicID/memberships/promote", h)
games.Post("/:gameID/clans/:clanPublicID/memberships/demote", h)
}
app.Get("/anything/*anythingparameter", func(ctx *iris.Context){
s := ctx.Param("anythingparameter")
ctx.Writef("The path after /anything is: %s",s)
})
mysubdomain:= app.Party("mysubdomain.")
// http://mysubdomain.myhost.com/
mysudomain.Get("/", h)
app.Listen("myhost.com:80")
}
func h(ctx *iris.Context) {
ctx.HTML(iris.StatusOK, "<h1>Path<h1/>"+ctx.Path())
}
Grouping routes that can (optionally) share the same middleware handlers, template layout and path prefix.
Example code:
users:= app.Party("/users", myAuthHandler)
// http://myhost.com/users/42/profile
users.Get("/:userid/profile", userProfileHandler) // httprouter path parameters
// http://myhost.com/users/messages/1
users.Get("/inbox/:messageid", userMessageHandler)
app.Listen("myhost.com:80")
Custom HTTP Errors page
With iris users are able to register their own handlers for http statuses like 404 not found, 500 internal server error and so on.
Example code:
// when 404 then render the template $templatedir/errors/404.html
// *read below for information about the view engine.*
app.OnError(iris.StatusNotFound, func(ctx *iris.Context){
ctx.RenderWithstatus(iris.StatusNotFound, "errors/404.html", nil)
})
app.OnError(500, func(ctx *iris.Context){
// ...
})
Custom http errors can be also be registered to a specific group of routes.
Example code:
games:= app.Party("/games", gamesMiddleware)
{
games.Get("/{gameID:[0-9]+}/clans", h) // gorillamux path parameters
games.Get("/{gameID:[0-9]+}/clans/clan/{publicID:[0-9]+}", h)
games.Get("/{gameID:[0-9]+}/clans/search", h)
}
games.OnError(iris.StatusNotFound, gamesNotFoundHandler)
Middleware ecosystem.
Middleware is just a concept of ordered chain of handlers.
Middleware can be registered globally, per-party, per-subdomain and per-route.
Example code:
// globally
// before any routes, appends the middleware to all routes
app.UseFunc(func(ctx *iris.Context){
// ... any code here
ctx.Next() // in order to continue to the next handler,
// if that is missing then the next in chain handlers will be not executed,
// useful for authentication middleware
})
// globally
// after or before any routes, prepends the middleware to all routes
app.UseGlobalFunc(handlerFunc1, handlerFunc2, handlerFunc3)
// per-route
app.Post("/login", authenticationHandler, loginPageHandler)
// per-party(group of routes)
users := app.Party("/users", usersMiddleware)
users.Get("/", usersIndex)
// per-subdomain
mysubdomain := app.Party("mysubdomain.", firstMiddleware)
mysubdomain.UseFunc(secondMiddleware)
mysubdomain.Get("/", mysubdomainIndex)
// per wildcard, dynamic subdomain
dynamicSub := app.Party(".*", firstMiddleware, secondMiddleware)
dynamicSub.Get("/", func(ctx *iris.Context){
ctx.Writef("Hello from subdomain: "+ ctx.Subdomain())
})
`iris.ToHandler` converts(by wrapping) any `http.Handler/HandlerFunc` or
`func(w http.ResponseWriter,r *http.Request, next http.HandlerFunc)` to an `iris.HandlerFunc`.
iris.ToHandler(nativeNethttpHandler)
Let's convert the https://github.com/rs/cors net/http external middleware which returns a `next form` handler.
Example code:
package main
import (
"gopkg.in/kataras/iris.v6"
"github.com/kataras/adaptors/gorillamux"
"github.com/rs/cors"
)
// myCors returns a new cors middleware
// with the provided options.
myCors := func(opts cors.Options) iris.HandlerFunc {
handlerWithNext := cors.New(opts).ServeHTTP
// this is the only func you will have to use if you're going to make use of any external net/http middleware.
// iris.ToHandler converts the net/http middleware to an iris-compatible.
return iris.ToHandler(handlerWithNext)
}
func main(){
app := iris.New()
app.Adapt(httprouter.New())
// Any registers a route to all http methods.
app.Any("/user", myCors(cors.Options{AllowOrigins: "*"}), func(ctx *iris.Context){
// ....
})
app.Listen(":8080")
}
Visit https://godoc.org/github.com/kataras/iris#Router for more.
View engine, supports 5 template engines, developers can still use any external golang template engine,
as `context.ResponseWriter` is an `io.Writer`.
All of these five template engines have common features with common API,
like Layout, Template Funcs, Party-specific layout, partial rendering and more.
- the standard html, based on https://github.com/kataras/go-template/tree/master/html
its template parser is the https://golang.org/pkg/html/template/.
- django, based on https://github.com/kataras/go-template/tree/master/django
its template parser is the https://github.com/flosch/pongo2
- pug, based on https://github.com/kataras/go-template/tree/master/pug
its template parser is the https://github.com/Joker/jade
- handlebars, based on https://github.com/kataras/go-template/tree/master/handlebars
its template parser is the https://github.com/aymerick/raymond
- amber, based on https://github.com/kataras/go-template/tree/master/amber
its template parser is the https://github.com/eknkc/amber
Each one of these template engines has different options,
view adaptors are located here: https://github.com/kataras/iris/tree/master/adaptors/view .
Example code:
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/gorillamux"
"gopkg.in/kataras/iris.v6/adaptors/view" // <--- it contains all the template engines
)
func main() {
app := iris.New(iris.Configuration{Gzip: false, Charset: "UTF-8"}) // defaults to these
app.Adapt(iris.DevLogger())
app.Adapt(gorillamux.New())
// - standard html | view.HTML(...)
// - django | view.Django(...)
// - pug(jade) | view.Pug(...)
// - handlebars | view.Handlebars(...)
// - amber | view.Amber(...)
app.Adapt(view.HTML("./templates", ".html")) // <---- use the standard html
// default template funcs:
//
// - {{ url "mynamedroute" "pathParameter_ifneeded"} }
// - {{ urlpath "mynamedroute" "pathParameter_ifneeded" }}
// - {{ render "header.html" }}
// - {{ render_r "header.html" }} // partial relative path to current page
// - {{ yield }}
// - {{ current }}
//
// to adapt custom funcs, use:
app.Adapt(iris.TemplateFuncsPolicy{"myfunc": func(s string) string {
return "hi "+s
}}) // usage inside template: {{ hi "kataras"}}
app.Get("/hi", func(ctx *iris.Context) {
ctx.Render(
// the file name of the template relative to the './templates'.
"hi.html",
iris.Map{"Name": "Iris"},
// the .Name inside the ./templates/hi.html,
// you can use any custom struct that you want to bind to the requested template.
iris.Map{"gzip": false}, // set to true to enable gzip compression.
)
})
// http://127.0.0.1:8080/hi
app.Listen(":8080")
}
View engine supports bundled(https://github.com/jteeuwen/go-bindata) template files too.
go-bindata gives you two functions, asset and assetNames,
these can be setted to each of the template engines using the `.Binary` func.
Example code:
djangoEngine := view.Django("./templates", ".html")
djangoEngine.Binary(asset, assetNames)
app.Adapt(djangoEngine)
A real example can be found here: https://github.com/kataras/iris/tree/v6/adaptors/view/_examples/template_binary .
Enable auto-reloading of templates on each request. Useful while users are in dev mode
because they don't have to restart their app on every edit you make on the template files.
Example code:
pugEngine := view.Pug("./templates", ".jade")
pugEngine.Reload(true) // <--- set to true to re-build the templates on each request.
app.Adapt(pugEngine)
You should have a basic idea of the framework by now, we just scratched the surface.
If you enjoy what you just saw and want to learn more, please follow the below links:
- examples: https://github.com/iris-contrib/examples
- book: https://docs.iris-go.com
- adaptors: https://github.com/kataras/iris/tree/v6/adaptors
- middleware: https://github.com/kataras/iris/tree/v6/middleware & https://github.com/iris-contrib/middleware
- godocs: https://godoc.org/github.com/kataras/iris
*/
package iris // import "gopkg.in/kataras/iris.v6"

View File

@ -1,865 +0,0 @@
Quick Start
-----------
```bash
go get -u github.com/kataras/iris/iris
```
```sh
cat app.go
```
```go
package iris_test
import (
"github.com/kataras/go-template/html"
"github.com/kataras/iris"
)
func main() {
app := iris.New()
// 6 template engines are supported out-of-the-box:
//
// - standard html/template
// - amber
// - django
// - handlebars
// - pug(jade)
// - markdown
//
// Use the html standard engine for all files inside "./views" folder with extension ".html"
// Defaults to:
app.UseTemplate(html.New()).Directory("./views", ".html")
// http://localhost:6111
// Method: "GET"
// Render ./views/index.html
app.Get("/", func(ctx *iris.Context) {
ctx.Render("index.html", nil)
})
// Group routes, optionally: share middleware, template layout and custom http errors.
userAPI := app.Party("/users", userAPIMiddleware).
Layout("layouts/userLayout.html")
{
// Fire userNotFoundHandler when Not Found
// inside http://localhost:6111/users/*anything
userAPI.OnError(404, userNotFoundHandler)
// http://localhost:6111/users
// Method: "GET"
userAPI.Get("/", getAllHandler)
// http://localhost:6111/users/42
// Method: "GET"
userAPI.Get("/:id", getByIDHandler)
// http://localhost:6111/users
// Method: "POST"
userAPI.Post("/", saveUserHandler)
}
// Start the server at 0.0.0.0:6111
app.Listen(":6111")
}
func getByIDHandler(ctx *iris.Context) {
// take the :id from the path, parse to integer
// and set it to the new userID local variable.
userID, _ := ctx.ParamInt("id")
// userRepo, imaginary database service <- your only job.
user := userRepo.GetByID(userID)
// send back a response to the client,
// .JSON: content type as application/json; charset="utf-8"
// iris.StatusOK: with 200 http status code.
//
// send user as it is or make use of any json valid golang type,
// like the iris.Map{"username" : user.Username}.
ctx.JSON(iris.StatusOK, user)
}
```
> TIP: $ iris run main.go to enable hot-reload on .go source code changes.
> TIP: iris.Config.IsDevelopment = true to monitor the changes you make in the templates.
> TIP: Want to change the default Router's behavior to something else like Gorilla's Mux?
Go [there](https://github.com/iris-contrib/examples/tree/master/plugin_gorillamux) to learn how.
### New
```go
// New with default configuration
app := iris.New()
app.Listen(....)
// New with configuration struct
app := iris.New(iris.Configuration{ IsDevelopment: true})
app.Listen(...)
// Default station
iris.Listen(...)
// Default station with custom configuration
// view the whole configuration at: ./configuration.go
iris.Config.IsDevelopment = true
iris.Config.Charset = "UTF-8"
iris.Listen(...)
```
### Listening
`Serve(ln net.Listener) error`
```go
ln, err := net.Listen("tcp4", ":8080")
if err := iris.Serve(ln); err != nil {
panic(err)
}
```
`Listen(addr string)`
```go
iris.Listen(":8080")
```
`ListenTLS(addr string, certFile, keyFile string)`
```go
iris.ListenTLS(":8080", "./ssl/mycert.cert", "./ssl/mykey.key")
```
`ListenLETSENCRYPT(addr string, cacheFileOptional ...string)`
```go
iris.ListenLETSENCRYPT("mydomain.com")
```
```go
iris.Serve(iris.LETSENCRYPTPROD("myproductionwebsite.com"))
```
And
```go
ListenUNIX(addr string, mode os.FileMode)
Close() error
Reserve() error
IsRunning() bool
```
### Routing
```go
iris.Get("/products/:id", getProduct)
iris.Post("/products", saveProduct)
iris.Put("products/:id", editProduct)
iris.Delete("/products/:id", deleteProduct)
```
And
```go
iris.Patch("", ...)
iris.Connect("", ...)
iris.Options("", ...)
iris.Trace("", ...)
```
### Path Parameters
```go
func getProduct(ctx *iris.Context){
// Get id from path '/products/:id'
id := ctx.Param("id")
}
```
### Query Parameters
`/details?color=blue&weight=20`
```go
func details(ctx *iris.Context){
color := ctx.URLParam("color")
weight,_ := ctx.URLParamInt("weight")
}
```
### Form `application/x-www-form-urlencoded`
`METHOD: POST | PATH: /save`
name | value
:--- | :---
name | Gerasimos Maropoulos
email | kataras2006@homail.com
```go
func save(ctx *iris.Context) {
// Get name and email
name := ctx.FormValue("name")
email := ctx.FormValue("email")
}
```
### Form `multipart/form-data`
`POST` `/save`
name | value
:--- | :---
name | Gerasimos Maropoulos
email | kataras2006@hotmail.com
avatar | avatar
```go
func save(ctx *iris.Context) {
// Get name and email
name := ctx.FormValue("name")
email := ctx.FormValue("email")
// Get avatar
avatar, info, err := ctx.FormFile("avatar")
if err != nil {
ctx.EmitError(iris.StatusInternalServerError)
return
}
defer avatar.Close()
// Destination
dst, err := os.Create(avatar.Filename)
if err != nil {
ctx.EmitError(iris.StatusInternalServerError)
return
}
defer dst.Close()
// Copy
if _, err = io.Copy(dst, avatar); err != nil {
ctx.EmitError(iris.StatusInternalServerError)
return
}
ctx.HTML(iris.StatusOK, "<b>Thanks!</b>")
}
```
### Handling Request
- Bind `JSON` or `XML` or `form` payload into Go struct based on `Content-Type` request header.
- Render response as `JSON` or `XML` with status code.
```go
type User struct {
Name string `json:"name" xml:"name" form:"name"`
Email string `json:"email" xml:"email" form:"email"`
}
iris.Post("/users", func(ctx *iris.Context) {
u := new(User)
if err := ctx.ReadJSON(u); err != nil {
ctx.EmitError(iris.StatusInternalServerError)
return
}
ctx.JSON(iris.StatusCreated, u)
// or
// ctx.XML(iris.StatusCreated, u)
// ctx.JSONP(...)
// ctx.HTML(iris.StatusCreated, "<b>Hi "+u.Name+"</b>")
// ctx.Markdown(iris.StatusCreated, "## Name: "+u.Name)
})
```
| Name | Description | Usage |
| ------------------|:---------------------:|-------:|
| [JSON ](https://github.com/kataras/go-serializer/tree/master/json) | JSON Serializer (Default) |[example 1](https://github.com/iris-contrib/examples/blob/master/serialize_engines/json_1/main.go),[example 2](https://github.com/iris-contrib/examples/blob/master/serialize_engines/json_2/main.go), [book section](https://docs.iris-go.com/serialize-engines.html)
| [JSONP ](https://github.com/kataras/go-serializer/tree/master/jsonp) | JSONP Serializer (Default) |[example 1](https://github.com/iris-contrib/examples/blob/master/serialize_engines/jsonp_1/main.go),[example 2](https://github.com/iris-contrib/examples/blob/master/serialize_engines/jsonp_2/main.go), [book section](https://docs.iris-go.com/serialize-engines.html)
| [XML ](https://github.com/kataras/go-serializer/tree/master/xml) | XML Serializer (Default) |[example 1](https://github.com/iris-contrib/examples/blob/master/serialize_engines/xml_1/main.go),[example 2](https://github.com/iris-contrib/examples/blob/master/serialize_engines/xml_2/main.go), [book section](https://docs.iris-go.com/serialize-engines.html)
| [Markdown ](https://github.com/kataras/go-serializer/tree/master/markdown) | Markdown Serializer (Default) |[example 1](https://github.com/iris-contrib/examples/blob/master/serialize_engines/markdown_1/main.go),[example 2](https://github.com/iris-contrib/examples/blob/master/serialize_engines/markdown_2/main.go), [book section](https://docs.iris-go.com/serialize-engines.html)
| [Text](https://github.com/kataras/go-serializer/tree/master/text) | Text Serializer (Default) |[example 1](https://github.com/iris-contrib/examples/blob/master/serialize_engines/text_1/main.go), [book section](https://docs.iris-go.com/serialize-engines.html)
| [Binary Data ](https://github.com/kataras/go-serializer/tree/master/data) | Binary Data Serializer (Default) |[example 1](https://github.com/iris-contrib/examples/blob/master/serialize_engines/data_1/main.go), [book section](https://docs.iris-go.com/serialize-engines.html)
### HTTP Errors
You can define your own handlers when http error occurs.
```go
package main
import (
"github.com/kataras/iris"
)
func main() {
iris.OnError(iris.StatusInternalServerError, func(ctx *iris.Context) {
ctx.Writef("CUSTOM 500 INTERNAL SERVER ERROR PAGE")
// or ctx.Render, ctx.HTML any render method you want
ctx.Log("http status: 500 happened!")
})
iris.OnError(iris.StatusNotFound, func(ctx *iris.Context) {
ctx.Writef("CUSTOM 404 NOT FOUND ERROR PAGE")
ctx.Log("http status: 404 happened!")
})
// emit the errors to test them
iris.Get("/500", func(ctx *iris.Context) {
ctx.EmitError(iris.StatusInternalServerError) // ctx.Panic()
})
iris.Get("/404", func(ctx *iris.Context) {
ctx.EmitError(iris.StatusNotFound) // ctx.NotFound()
})
iris.Listen(":80")
}
```
### Static Content
Serve files or directories, use the correct for your case, if you don't know which one, just use the `StaticWeb(reqPath string, systemPath string)`.
```go
// Favicon serves static favicon
// accepts 2 parameters, second is optional
// favPath (string), declare the system directory path of the __.ico
// requestPath (string), it's the route's path, by default this is the "/favicon.ico" because some browsers tries to get this by default first,
// you can declare your own path if you have more than one favicon (desktop, mobile and so on)
//
// this func will add a route for you which will static serve the /yuorpath/yourfile.ico to the /yourfile.ico (nothing special that you can't handle by yourself)
// Note that you have to call it on every favicon you have to serve automatically (desktop, mobile and so on)
//
// panics on error
Favicon(favPath string, requestPath ...string) RouteNameFunc
// StaticHandler returns a new Handler which serves static files
StaticHandler(reqPath string, systemPath string, showList bool, enableGzip bool) HandlerFunc
// StaticWeb same as Static but if index.html e
// xists 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)
StaticWeb(reqPath string, systemPath string) RouteNameFunc
// StaticEmbedded used when files are distributed inside the app executable, using go-bindata mostly
// First parameter is the request path, the path which the files in the vdir will be served to, for example "/static"
// Second parameter is the (virtual) directory path, for example "./assets"
// Third parameter is the Asset function
// Forth parameter is the AssetNames function
//
// For more take a look at the
// example: https://github.com/iris-contrib/examples/tree/master/static_files_embedded
StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) RouteNameFunc
// StaticContent serves bytes, memory cached, on the reqPath
// a good example of this is how the websocket server uses that to auto-register the /iris-ws.js
StaticContent(reqPath string, cType string, content []byte) RouteNameFunc
// 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 the second parameter is empty, otherwise the requestPath is the second parameter
// it uses gzip compression (compression on each request, no file cache)
StaticServe(systemPath string, requestPath ...string)
```
```go
iris.StaticWeb("/public", "./static/assets/")
//-> /public/assets/favicon.ico
```
```go
iris.StaticWeb("/","./my_static_html_website")
```
```go
context.StaticServe(systemPath string, requestPath ...string)
```
#### Manual static file serving
```go
// ServeFile serves a view file, to send a file
// 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
context.ServeFile(filename string, gzipCompression bool) error
```
Serve static individual file
```go
iris.Get("/txt", func(ctx *iris.Context) {
ctx.ServeFile("./myfolder/staticfile.txt", false)
}
```
### Templates
**HTML Template Engine, defaulted**
```html
<!-- file ./templates/hi.html -->
<html>
<head>
<title>Hi Iris</title>
</head>
<body>
<h1>Hi {{.Name}}
</body>
</html>
```
```go
// file ./main.go
package main
import "github.com/kataras/iris"
func main() {
iris.Config.IsDevelopment = true // this will reload the templates on each request
iris.Get("/hi", hi)
iris.Listen(":8080")
}
func hi(ctx *iris.Context) {
ctx.MustRender("hi.html", struct{ Name string }{Name: "iris"})
}
```
| Name | Description | Usage |
| ------------------|:---------------------:|-------:|
| [HTML/Default Engine ](https://github.com/kataras/go-template/tree/master/html) | HTML Template Engine (Default) |[example ](https://github.com/iris-contrib/examples/blob/master/template_engines/template_html_0/main.go), [book section](https://docs.iris-go.com/template-engines.html)
| [Django Engine ](https://github.com/kataras/go-template/tree/master/django) | Django Template Engine |[example ](https://github.com/iris-contrib/examples/blob/master/template_engines/template_django_1/main.go), [book section](https://docs.iris-go.com/template-engines.html)
| [Pug/Jade Engine ](https://github.com/kataras/go-template/tree/master/pug) | Pug Template Engine |[example ](https://github.com/iris-contrib/examples/blob/master/template_engines/template_pug_1/main.go), [book section](https://docs.iris-go.com/template-engines.html)
| [Handlebars Engine ](https://github.com/kataras/go-template/tree/master/handlebars) | Handlebars Template Engine |[example ](https://github.com/iris-contrib/examples/blob/master/template_engines/template_handlebars_1/main.go), [book section](https://docs.iris-go.com/template-engines.html)
| [Amber Engine ](https://github.com/kataras/go-template/tree/master/amber) | Amber Template Engine |[example ](https://github.com/iris-contrib/examples/blob/master/template_engines/template_amber_1/main.go), [book section](https://docs.iris-go.com/template-engines.html)
| [Markdown Engine ](https://github.com/kataras/go-template/tree/master/markdown) | Markdown Template Engine |[example ](https://github.com/iris-contrib/examples/blob/master/template_engines/template_markdown_1/main.go), [book section](https://docs.iris-go.com/template-engines.html)
> Each section of the README has its own - more advanced - subject on the book, so be sure to check book for any further research
[Read more](https://docs.iris-go.com/template-engines.html)
### Middleware ecosystem
```go
import (
"github.com/iris-contrib/middleware/logger"
"github.com/iris-contrib/middleware/cors"
"github.com/iris-contrib/middleware/basicauth"
)
// Root level middleware
iris.Use(logger.New())
iris.Use(cors.Default())
// Group level middleware
authConfig := basicauth.Config{
Users: map[string]string{"myusername": "mypassword", "mySecondusername": "mySecondpassword"},
Realm: "Authorization Required", // if you don't set it it's "Authorization Required"
ContextKey: "mycustomkey", // if you don't set it it's "user"
Expires: time.Duration(30) * time.Minute,
}
authentication := basicauth.New(authConfig)
g := iris.Party("/admin")
g.Use(authentication)
// Route level middleware
logme := func(ctx *iris.Context) {
println("request to /products")
ctx.Next()
}
iris.Get("/products", logme, func(ctx *iris.Context) {
ctx.Text(iris.StatusOK, "/products")
})
```
| Name | Description | Usage |
| ------------------|:---------------------:|-------:|
| [Basicauth Middleware ](https://github.com/iris-contrib/middleware/tree/master/basicauth) | HTTP Basic authentication |[example 1](https://github.com/iris-contrib/examples/blob/master/middleware_basicauth_1/main.go), [example 2](https://github.com/iris-contrib/examples/blob/master/middleware_basicauth_2/main.go), [book section](https://docs.iris-go.com/basic-authentication.html) |
| [JWT Middleware ](https://github.com/iris-contrib/middleware/tree/master/jwt) | JSON Web Tokens |[example ](https://github.com/iris-contrib/examples/blob/master/middleware_jwt/main.go), [book section](https://docs.iris-go.com/jwt.html) |
| [Cors Middleware ](https://github.com/iris-contrib/middleware/tree/master/cors) | Cross Origin Resource Sharing W3 specification | [how to use ](https://github.com/iris-contrib/middleware/tree/master/cors#how-to-use) |
| [Secure Middleware ](https://github.com/iris-contrib/middleware/tree/master/secure) | Facilitates some quick security wins | [example](https://github.com/iris-contrib/examples/blob/master/middleware_secure/main.go) |
| [I18n Middleware ](https://github.com/iris-contrib/middleware/tree/master/i18n) | Simple internationalization | [example](https://github.com/iris-contrib/examples/tree/master/middleware_internationalization_i18n), [book section](https://docs.iris-go.com/middleware-internationalization-and-localization.html) |
| [Recovery Middleware ](https://github.com/iris-contrib/middleware/tree/master/recovery) | Safety recover the station from panic | [example](https://github.com/iris-contrib/examples/blob/master/middleware_recovery/main.go) |
| [Logger Middleware ](https://github.com/iris-contrib/middleware/tree/master/logger) | Logs every request | [example](https://github.com/iris-contrib/examples/blob/master/middleware_logger/main.go), [book section](https://docs.iris-go.com/logger.html) |
| [LoggerZap Middleware ](https://github.com/iris-contrib/middleware/tree/master/loggerzap) | Logs every request using zap | [example](https://github.com/iris-contrib/examples/blob/master/middleware_logger/main.go), [book section](https://docs.iris-go.com/logger.html) |
| [Profile Middleware ](https://github.com/iris-contrib/middleware/tree/master/pprof) | Http profiling for debugging | [example](https://github.com/iris-contrib/examples/blob/master/middleware_pprof/main.go) |
| [Editor Plugin](https://github.com/iris-contrib/plugin/tree/master/editor) | Alm-tools, a typescript online IDE/Editor | [book section](https://docs.iris-go.com/plugin-editor.html) |
| [Typescript Plugin](https://github.com/iris-contrib/plugin/tree/master/typescript) | Auto-compile client-side typescript files | [book section](https://docs.iris-go.com/plugin-typescript.html) |
| [OAuth,OAuth2 Plugin](https://github.com/iris-contrib/plugin/tree/master/oauth) | User Authentication was never be easier, supports >27 providers | [example](https://github.com/iris-contrib/examples/tree/master/plugin_oauth_oauth2), [book section](https://docs.iris-go.com/plugin-oauth.html) |
| [Iris control Plugin](https://github.com/iris-contrib/plugin/tree/master/iriscontrol) | Basic (browser-based) control over your Iris station | [example](https://github.com/iris-contrib/examples/blob/master/plugin_iriscontrol/main.go), [book section](https://docs.iris-go.com/plugin-iriscontrol.html) |
> NOTE: All net/http handlers and middleware that already created by other go developers are also compatible with Iris, even if they are not be documented here, read more [here](https://github.com/iris-contrib/middleware#can-i-use-standard-nethttp-handler-with-iris).
### Sessions
If you notice a bug or issue [post it here](https://github.com/kataras/go-sessions).
- Cleans the temp memory when a session is idle, and re-allocates it to the temp memory when it's necessary.
The most used sessions are optimized to be in the front of the memory's list.
- Supports any type of database, currently only [Redis](https://github.com/kataras/go-sessions/tree/master/sessiondb/redis) and [LevelDB](https://github.com/kataras/go-sessions/tree/master/sessiondb/leveldb).
**A session can be defined as a server-side storage of information that is desired to persist throughout the user's interaction with the web application**.
Instead of storing large and constantly changing data via cookies in the user's browser (i.e. CookieStore),
**only a unique identifier is stored on the client side** called a "session id".
This session id is passed to the web server on every request.
The web application uses the session id as the key for retrieving the stored data from the database/memory. The session data is then available inside the iris.Context.
```go
iris.Get("/", func(ctx *iris.Context) {
ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
})
iris.Get("/set", func(ctx *iris.Context) {
//set session values
ctx.Session().Set("name", "iris")
//test if setted here
ctx.Writef("All ok session setted to: %s", ctx.Session().GetString("name"))
})
iris.Get("/get", func(ctx *iris.Context) {
// get a specific key as a string.
// returns an empty string if the key was not found.
name := ctx.Session().GetString("name")
ctx.Writef("The name on the /set was: %s", name)
})
iris.Get("/delete", func(ctx *iris.Context) {
// delete a specific key
ctx.Session().Delete("name")
})
iris.Get("/clear", func(ctx *iris.Context) {
// removes all entries
ctx.Session().Clear()
})
iris.Get("/destroy", func(ctx *iris.Context) {
// destroy/removes the entire session and cookie
ctx.SessionDestroy()
ctx.Log("You have to refresh the page to completely remove the session (on browsers), so the name should NOT be empty NOW, is it?\n ame: %s\n\nAlso check your cookies in your browser's cookies, should be no field for localhost/127.0.0.1 (or whatever you use)", ctx.Session().GetString("name"))
ctx.Writef("You have to refresh the page to completely remove the session (on browsers), so the name should NOT be empty NOW, is it?\nName: %s\n\nAlso check your cookies in your browser's cookies, should be no field for localhost/127.0.0.1 (or whatever you use)", ctx.Session().GetString("name"))
})
iris.Listen(":8080")
```
- `iris.DestroySessionByID(string)`
```go
// DestroySessionByID removes the session entry
// from the server-side memory (and database if registered).
// Client's session cookie will still exist but it will be reseted on the next request.
//
// It's safe to use it even if you are not sure if a session with that id exists.
DestroySessionByID(string)
```
- `iris.DestroyAllSessions()`
```go
// DestroyAllSessions removes all sessions
// from the server-side memory (and database if registered).
// Client's session cookie will still exist but it will be reseted on the next request.
DestroyAllSessions()
```
> Each section of the README has its own - more advanced - subject on the book, so be sure to check book for any further research
[Read more](https://docs.iris-go.com/package-sessions.html)
### Websockets
Server configuration
```go
iris.Config.Websocket{
// WriteTimeout time allowed to write a message to the connection.
// Default value is 15 * time.Second
WriteTimeout time.Duration
// PongTimeout allowed to read the next pong message from the connection
// Default value is 60 * time.Second
PongTimeout time.Duration
// PingPeriod send ping messages to the connection with this period. Must be less than PongTimeout
// Default value is (PongTimeout * 9) / 10
PingPeriod time.Duration
// MaxMessageSize max message size allowed from connection
// Default value is 1024
MaxMessageSize int64
// BinaryMessages set it to true in order to denotes binary data messages instead of utf-8 text
// see https://github.com/kataras/iris/issues/387#issuecomment-243006022 for more
// Defaults to false
BinaryMessages bool
// Endpoint is the path which the websocket server will listen for clients/connections
// Default value is empty string, if you don't set it the Websocket server is disabled.
Endpoint string
// ReadBufferSize is the buffer size for the underline reader
ReadBufferSize int
// WriteBufferSize is the buffer size for the underline writer
WriteBufferSize int
// Error specifies the function for generating HTTP error responses.
//
// The default behavior is to store the reason in the context (ctx.Set(reason)) and fire any custom error (ctx.EmitError(status))
Error func(ctx *Context, status int, reason error)
// CheckOrigin returns true if the request Origin header is acceptable. If
// CheckOrigin is nil, the host in the Origin header must not be set or
// must match the host of the request.
//
// The default behavior is to allow all origins
// you can change this behavior by setting the iris.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin
CheckOrigin func(r *http.Request) bool
// IDGenerator used to create (and later on, set)
// an ID for each incoming websocket connections (clients).
// If empty then the ID is generated by the result of 64
// random combined characters
IDGenerator func(r *http.Request) string
}
```
Connection's methods
```go
ID() string
Request() *http.Request
// Receive from the client
On("anyCustomEvent", func(message string) {})
On("anyCustomEvent", func(message int){})
On("anyCustomEvent", func(message bool){})
On("anyCustomEvent", func(message anyCustomType){})
On("anyCustomEvent", func(){})
// Receive a native websocket message from the client
// compatible without need of import the iris-ws.js to the .html
OnMessage(func(message []byte){})
// Send to the client
Emit("anyCustomEvent", string)
Emit("anyCustomEvent", int)
Emit("anyCustomEvent", bool)
Emit("anyCustomEvent", anyCustomType)
// Send native websocket messages
// with config.BinaryMessages = true
// useful when you use proto or something like this.
EmitMessage([]byte("anyMessage"))
// Send to specific client(s)
To("otherConnectionId").Emit/EmitMessage...
To("anyCustomRoom").Emit/EmitMessage...
// Send to all opened connections/clients
To(websocket.All).Emit/EmitMessage...
// Send to all opened connections/clients EXCEPT this client
To(websocket.Broadcast).Emit/EmitMessage...
// Rooms, group of connections/clients
Join("anyCustomRoom")
Leave("anyCustomRoom")
// Fired when the connection is closed
OnDisconnect(func(){})
// Force-disconnect the client from the server-side
Disconnect() error
```
```go
// file ./main.go
package main
import (
"fmt"
"github.com/kataras/iris"
)
type clientPage struct {
Title string
Host string
}
func main() {
iris.Static("/js", "./static/js", 1)
iris.Get("/", func(ctx *iris.Context) {
ctx.Render("client.html", clientPage{"Client Page", ctx.Host()})
})
// the path at which the websocket client should register itself to
iris.Config.Websocket.Endpoint = "/my_endpoint"
var myChatRoom = "room1"
iris.Websocket.OnConnection(func(c iris.WebsocketConnection) {
c.Join(myChatRoom)
c.On("chat", func(message string) {
// to all except this connection ->
//c.To(iris.Broadcast).Emit("chat", "Message from: "+c.ID()+"-> "+message)
// to the client ->
//c.Emit("chat", "Message from myself: "+message)
// send the message to the whole room,
// all connections which are inside this room will receive this message
c.To(myChatRoom).Emit("chat", "From: "+c.ID()+": "+message)
})
c.OnDisconnect(func() {
fmt.Printf("\nConnection with ID: %s has been disconnected!", c.ID())
})
})
iris.Listen(":8080")
}
```
```js
// file js/chat.js
var messageTxt;
var messages;
$(function () {
messageTxt = $("#messageTxt");
messages = $("#messages");
ws = new Ws("ws://" + HOST + "/my_endpoint");
ws.OnConnect(function () {
console.log("Websocket connection enstablished");
});
ws.OnDisconnect(function () {
appendMessage($("<div><center><h3>Disconnected</h3></center></div>"));
});
ws.On("chat", function (message) {
appendMessage($("<div>" + message + "</div>"));
})
$("#sendBtn").click(function () {
//ws.EmitMessage(messageTxt.val());
ws.Emit("chat", messageTxt.val().toString());
messageTxt.val("");
})
})
function appendMessage(messageDiv) {
var theDiv = messages[0]
var doScroll = theDiv.scrollTop == theDiv.scrollHeight - theDiv.clientHeight;
messageDiv.appendTo(messages)
if (doScroll) {
theDiv.scrollTop = theDiv.scrollHeight - theDiv.clientHeight;
}
}
```
```html
<!-- file templates/client.html -->
<html>
<head>
<title>My iris-ws</title>
</head>
<body>
<div id="messages" style="border-width:1px;border-style:solid;height:400px;width:375px;">
</div>
<input type="text" id="messageTxt" />
<button type="button" id="sendBtn">Send</button>
<script type="text/javascript">
var HOST = {{.Host}}
</script>
<script src="js/vendor/jquery-2.2.3.min.js" type="text/javascript"></script>
<!-- /iris-ws.js is served automatically by the server -->
<script src="/iris-ws.js" type="text/javascript"></script>
<!-- -->
<script src="js/chat.js" type="text/javascript"></script>
</body>
</html>
```
View a working example by navigating [here](https://github.com/iris-contrib/examples/tree/master/websocket) and if you need more than one websocket server [click here](https://github.com/iris-contrib/examples/tree/master/websocket_unlimited_servers).
> Each section of the README has its own - more advanced - subject on the book, so be sure to check book for any further research
[Read more](https://docs.iris-go.com/package-websocket.html)
Benchmarks
------------
These benchmarks are for the previous Iris version(1month ago), new benchmarks are coming after the release of the Go version 1.8 in order to include the `Push` feature inside the tests.
This Benchmark test aims to compare the whole HTTP request processing between Go web frameworks.
![Benchmark Wizzard July 21, 2016- Processing Time Horizontal Graph](https://raw.githubusercontent.com/smallnest/go-web-framework-benchmark/4db507a22c964c9bc9774c5b31afdc199a0fe8b7/benchmark.png)
**The results have been updated on July 21, 2016**
Depends on:
- http protocol layer comes from [net/http](https://github.com/golang/go/tree/master/src/net/http), by Go Authors.
- rich and encoded responses support comes from [kataras/go-serializer](https://github.com/kataras/go-serializer/tree/0.0.4), by me.
- template support comes from [kataras/go-template](https://github.com/kataras/go-template/tree/0.0.3), by me.
- gzip support comes from [kataras/go-fs](https://github.com/kataras/go-fs/tree/0.0.5) and the super-fast compression library [klauspost/compress/gzip](https://github.com/klauspost/compress/tree/master/gzip), by me & Klaus Post.
- websockets support comes from [kataras/go-websocket](https://github.com/kataras/go-websocket/tree/0.0.2), by me.
- Base of the parameterized routing algorithm comes from [julienschmidt/httprouter](https://github.com/julienschmidt/httprouter), by Julien Schmidt, with some relative to performance edits by me.
- sessions support comes from [kataras/go-sessions](https://github.com/kataras/go-sessions/tree/0.0.6), by me.
- caching support comes from [geekypanda/httpcache](https://github.com/geekypanda/httpcache/tree/0.0.1), by me and GeekyPanda.
- end-to-end http test APIs comes from [gavv/httpexpect](https://github.com/gavv/httpexpect), by Victor Gaydov.
- hot-reload on source code changes comes from [kataras/rizla](https://github.com/kataras/rizla), by me.
- auto-updater (via github) comes from [kataras/go-fs](https://github.com/kataras/go-fs), by me.
- request body form binder is an [edited version](https://github.com/iris-contrib/formBinder) of the [monoculum/formam](https://github.com/monoculum/formam) library, by Monoculum Organisation.
- all other packages comes from the [Iris Contrib Organisation](https://github.com/iris-contrib) and the [Go standard library](https://github.com/golang/go), by me & The Go Authors.

View File

@ -1,12 +0,0 @@
# Documentation
Navigate through [https://docs.iris-go.com/](https://docs.iris-go.com/) website to read the docs.
## Contributing
You can contribute to the documentation via PR to its public repository, [iris-contrib/gitbook](https://github.com/iris-contrib/gitbook). Any code-fix or even a grammar-fix is acceptable and welcomed!
## Examples?
Navigate through examples by clicking [here](https://github.com/iris-contrib/examples) & [run](https://github.com/kataras/iris/blob/master/examples/README.md) them.

View File

@ -1,16 +0,0 @@
# Examples
Navigate through [iris-contrib/examples](https://github.com/iris-contrib/examples) repository to view all available examples.
> These examples are small but practical, they do NOT cover all Iris' and Go's stdlib features.
## Run
1. Download the [Go Programming Language](https://golang.org/dl/).
2. Download the [LiteIDE](https://sourceforge.net/projects/liteide/files/X30.3/), a cross-platform Go IDE.
3. Click [here](https://github.com/iris-contrib/examples/archive/master.zip) to download all examples.
4. **Unzip** the contents of your `examples-master.zip` you just downloaded.
5. Open the LiteIDE, click on the `File -> Open Folder...` menu item (top-left corner)
and select the folder which contains the contents you just unzipped.
6. Open an example folder, select its `main.go` and `run` it by pressing `Ctrl/CMD +R`.

View File

@ -14,11 +14,11 @@ type StaticHandlerBuilder interface {
Gzip(enable bool) StaticHandlerBuilder
Listing(listDirectoriesOnOff bool) StaticHandlerBuilder
StripPath(yesNo bool) StaticHandlerBuilder
Except(r ...Route) StaticHandlerBuilder
Except(r ...RouteInfo) StaticHandlerBuilder
Build() HandlerFunc
}
type webfs struct {
type fsHandler struct {
// user options, only directory is required.
directory http.Dir
requestPath string
@ -28,7 +28,7 @@ type webfs struct {
// these are init on the Build() call
filesystem http.FileSystem
once sync.Once
exceptions []Route
exceptions []RouteInfo
handler HandlerFunc
}
@ -51,7 +51,7 @@ func toWebPath(systemPath string) string {
// this builder is used by people who have more complicated application
// structure and want a fluent api to work on.
func NewStaticHandlerBuilder(dir string) StaticHandlerBuilder {
return &webfs{
return &fsHandler{
directory: http.Dir(dir),
// default route path is the same as the directory
requestPath: toWebPath(dir),
@ -66,33 +66,33 @@ func NewStaticHandlerBuilder(dir string) StaticHandlerBuilder {
// Path sets the request path.
// Defaults to same as system path
func (w *webfs) Path(requestRoutePath string) StaticHandlerBuilder {
func (w *fsHandler) Path(requestRoutePath string) StaticHandlerBuilder {
w.requestPath = toWebPath(requestRoutePath)
return w
}
// Gzip if enable is true then gzip compression is enabled for this static directory
// Defaults to false
func (w *webfs) Gzip(enable bool) StaticHandlerBuilder {
func (w *fsHandler) Gzip(enable bool) StaticHandlerBuilder {
w.gzip = enable
return w
}
// Listing turn on/off the 'show files and directories'.
// Defaults to false
func (w *webfs) Listing(listDirectoriesOnOff bool) StaticHandlerBuilder {
func (w *fsHandler) Listing(listDirectoriesOnOff bool) StaticHandlerBuilder {
w.listDirectories = listDirectoriesOnOff
return w
}
func (w *webfs) StripPath(yesNo bool) StaticHandlerBuilder {
func (w *fsHandler) StripPath(yesNo bool) StaticHandlerBuilder {
w.stripPath = yesNo
return w
}
// Except add a route exception,
// gives priority to that Route over the static handler.
func (w *webfs) Except(r ...Route) StaticHandlerBuilder {
func (w *fsHandler) Except(r ...RouteInfo) StaticHandlerBuilder {
w.exceptions = append(w.exceptions, r...)
return w
}
@ -110,7 +110,7 @@ func (n noListFile) Readdir(count int) ([]os.FileInfo, error) {
// Implements the http.Filesystem
// Do not call it.
func (w *webfs) Open(name string) (http.File, error) {
func (w *fsHandler) Open(name string) (http.File, error) {
info, err := w.filesystem.Open(name)
if err != nil {
@ -125,11 +125,11 @@ func (w *webfs) Open(name string) (http.File, error) {
}
// Build the handler (once) and returns it
func (w *webfs) Build() HandlerFunc {
func (w *fsHandler) Build() HandlerFunc {
// we have to ensure that Build is called ONLY one time,
// one instance per one static directory.
w.once.Do(func() {
w.filesystem = http.Dir(w.directory)
w.filesystem = w.directory
// set the filesystem to itself in order to be recognised of listing property (can be change at runtime too)
fileserver := http.FileServer(w)

159
handler.go Normal file
View File

@ -0,0 +1,159 @@
package iris
import (
"net/http"
"github.com/kataras/go-errors"
)
// errHandler returns na error with message: 'Passed argument is not func(*Context) neither an object which implements the iris.Default.Handler with Serve(ctx *Context)
// It seems to be a +type Points to: +pointer.'
var errHandler = errors.New(`
Passed argument is not an iris.Handler (or func(*iris.Context)) neither one of these types:
- http.Handler
- func(w http.ResponseWriter, r *http.Request)
- func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)
---------------------------------------------------------------------
It seems to be a %T points to: %v`)
type (
// Handler the main Iris Handler interface.
Handler interface {
Serve(ctx *Context) // iris-specific
}
// HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
HandlerFunc func(*Context)
// Middleware is just a slice of Handler []func(c *Context)
Middleware []Handler
)
// Serve implements the Handler, is like ServeHTTP but for Iris
func (h HandlerFunc) Serve(ctx *Context) {
h(ctx)
}
// ToNativeHandler converts an iris handler to http.Handler
func ToNativeHandler(s *Framework, h Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s.Context.Run(w, r, func(ctx *Context) {
h.Serve(ctx)
})
})
}
// ToHandler converts different style of handlers that you
// used to use (usually with third-party net/http middleware) to an iris.HandlerFunc.
//
// Supported types:
// - .ToHandler(h http.Handler)
// - .ToHandler(func(w http.ResponseWriter, r *http.Request))
// - .ToHandler(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc))
func ToHandler(handler interface{}) HandlerFunc {
switch handler.(type) {
case HandlerFunc:
{
//
//it's already an iris handler
//
return handler.(HandlerFunc)
}
case http.Handler:
//
// handlerFunc.ServeHTTP(w,r)
//
{
h := handler.(http.Handler)
return func(ctx *Context) {
h.ServeHTTP(ctx.ResponseWriter, ctx.Request)
}
}
case func(http.ResponseWriter, *http.Request):
{
//
// handlerFunc(w,r)
//
return ToHandler(http.HandlerFunc(handler.(func(http.ResponseWriter, *http.Request))))
}
case func(http.ResponseWriter, *http.Request, http.HandlerFunc):
{
//
// handlerFunc(w,r, http.HandlerFunc)
//
return toHandlerNextHTTPHandlerFunc(handler.(func(http.ResponseWriter, *http.Request, http.HandlerFunc)))
}
default:
{
//
// No valid handler passed
//
panic(errHandler.Format(handler, handler))
}
}
}
func toHandlerNextHTTPHandlerFunc(h func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)) HandlerFunc {
return HandlerFunc(func(ctx *Context) {
// take the next handler in route's chain
nextIrisHandler := ctx.NextHandler()
if nextIrisHandler != nil {
executed := false // we need to watch this in order to StopExecution from all next handlers
// if this next handler is not executed by the third-party net/http next-style middleware.
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextIrisHandler.Serve(ctx)
executed = true
})
h(ctx.ResponseWriter, ctx.Request, nextHandler)
// after third-party middleware's job:
if executed {
// if next is executed then increment the ctx.Pos manually
// in order to the next handler not to be executed twice.
ctx.Pos++
} else {
// otherwise StopExecution from all next handlers.
ctx.StopExecution()
}
return
}
// if not next handler found then this is not a 'valid' middleware but
// some middleware may don't care about next,
// so we just execute the handler with an empty net.
h(ctx.ResponseWriter, ctx.Request, http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
})
}
// convertToHandlers just make []HandlerFunc to []Handler, although HandlerFunc and Handler are the same
// we need this on some cases we explicit want a interface Handler, it is useless for users.
func convertToHandlers(handlersFn []HandlerFunc) []Handler {
hlen := len(handlersFn)
mlist := make([]Handler, hlen)
for i := 0; i < hlen; i++ {
mlist[i] = Handler(handlersFn[i])
}
return mlist
}
// joinMiddleware uses to create a copy of all middleware and return them in order to use inside the node
func joinMiddleware(middleware1 Middleware, middleware2 Middleware) Middleware {
nowLen := len(middleware1)
totalLen := nowLen + len(middleware2)
// create a new slice of middleware in order to store all handlers, the already handlers(middleware) and the new
newMiddleware := make(Middleware, totalLen)
//copy the already middleware to the just created
copy(newMiddleware, middleware1)
//start from there we finish, and store the new middleware too
copy(newMiddleware[nowLen:], middleware2)
return newMiddleware
}

1544
http.go

File diff suppressed because it is too large Load Diff

View File

@ -1,792 +0,0 @@
// Black-box Testing
package iris_test
import (
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"os"
"strconv"
"testing"
"time"
"github.com/gavv/httpexpect"
"github.com/kataras/iris"
"github.com/kataras/iris/httptest"
)
const (
testTLSCert = `-----BEGIN CERTIFICATE-----
MIIDAzCCAeugAwIBAgIJAP0pWSuIYyQCMA0GCSqGSIb3DQEBBQUAMBgxFjAUBgNV
BAMMDWxvY2FsaG9zdDozMzEwHhcNMTYxMjI1MDk1OTI3WhcNMjYxMjIzMDk1OTI3
WjAYMRYwFAYDVQQDDA1sb2NhbGhvc3Q6MzMxMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA5vETjLa+8W856rWXO1xMF/CLss9vn5xZhPXKhgz+D7ogSAXm
mWP53eeBUGC2r26J++CYfVqwOmfJEu9kkGUVi8cGMY9dHeIFPfxD31MYX175jJQe
tu0WeUII7ciNsSUDyBMqsl7yi1IgN7iLONM++1+QfbbmNiEbghRV6icEH6M+bWlz
3YSAMEdpK3mg2gsugfLKMwJkaBKEehUNMySRlIhyLITqt1exYGaggRd1zjqUpqpD
sL2sRVHJ3qHGkSh8nVy8MvG8BXiFdYQJP3mCQDZzruCyMWj5/19KAyu7Cto3Bcvu
PgujnwRtU+itt8WhZUVtU1n7Ivf6lMJTBcc4OQIDAQABo1AwTjAdBgNVHQ4EFgQU
MXrBvbILQmiwjUj19aecF2N+6IkwHwYDVR0jBBgwFoAUMXrBvbILQmiwjUj19aec
F2N+6IkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA4zbFml1t9KXJ
OijAV8gALePR8v04DQwJP+jsRxXw5zzhc8Wqzdd2hjUd07mfRWAvmyywrmhCV6zq
OHznR+aqIqHtm0vV8OpKxLoIQXavfBd6axEXt3859RDM4xJNwIlxs3+LWGPgINud
wjJqjyzSlhJpQpx4YZ5Da+VMiqAp8N1UeaZ5lBvmSDvoGh6HLODSqtPlWMrldRW9
AfsXVxenq81MIMeKW2fSOoPnWZ4Vjf1+dSlbJE/DD4zzcfbyfgY6Ep/RrUltJ3ag
FQbuNTQlgKabe21dSL9zJ2PengVKXl4Trl+4t/Kina9N9Jw535IRCSwinD6a/2Ca
m7DnVXFiVA==
-----END CERTIFICATE-----
`
testTLSKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA5vETjLa+8W856rWXO1xMF/CLss9vn5xZhPXKhgz+D7ogSAXm
mWP53eeBUGC2r26J++CYfVqwOmfJEu9kkGUVi8cGMY9dHeIFPfxD31MYX175jJQe
tu0WeUII7ciNsSUDyBMqsl7yi1IgN7iLONM++1+QfbbmNiEbghRV6icEH6M+bWlz
3YSAMEdpK3mg2gsugfLKMwJkaBKEehUNMySRlIhyLITqt1exYGaggRd1zjqUpqpD
sL2sRVHJ3qHGkSh8nVy8MvG8BXiFdYQJP3mCQDZzruCyMWj5/19KAyu7Cto3Bcvu
PgujnwRtU+itt8WhZUVtU1n7Ivf6lMJTBcc4OQIDAQABAoIBAQCTLE0eHpPevtg0
+FaRUMd5diVA5asoF3aBIjZXaU47bY0G+SO02x6wSMmDFK83a4Vpy/7B3Bp0jhF5
DLCUyKaLdmE/EjLwSUq37ty+JHFizd7QtNBCGSN6URfpmSabHpCjX3uVQqblHIhF
mki3BQCdJ5CoXPemxUCHjDgYSZb6JVNIPJExjekc0+4A2MYWMXV6Wr86C7AY3659
KmveZpC3gOkLA/g/IqDQL/QgTq7/3eloHaO+uPBihdF56do4eaOO0jgFYpl8V7ek
PZhHfhuPZV3oq15+8Vt77ngtjUWVI6qX0E3ilh+V5cof+03q0FzHPVe3zBUNXcm0
OGz19u/FAoGBAPSm4Aa4xs/ybyjQakMNix9rak66ehzGkmlfeK5yuQ/fHmTg8Ac+
ahGs6A3lFWQiyU6hqm6Qp0iKuxuDh35DJGCWAw5OUS/7WLJtu8fNFch6iIG29rFs
s+Uz2YLxJPebpBsKymZUp7NyDRgEElkiqsREmbYjLrc8uNKkDy+k14YnAoGBAPGn
ZlN0Mo5iNgQStulYEP5pI7WOOax9KOYVnBNguqgY9c7fXVXBxChoxt5ebQJWG45y
KPG0hB0bkA4YPu4bTRf5acIMpjFwcxNlmwdc4oCkT4xqAFs9B/AKYZgkf4IfKHqW
P9PD7TbUpkaxv25bPYwUSEB7lPa+hBtRyN9Wo6qfAoGAPBkeISiU1hJE0i7YW55h
FZfKZoqSYq043B+ywo+1/Dsf+UH0VKM1ZSAnZPpoVc/hyaoW9tAb98r0iZ620wJl
VkCjgYklknbY5APmw/8SIcxP6iVq1kzQqDYjcXIRVa3rEyWEcLzM8VzL8KFXbIQC
lPIRHFfqKuMEt+HLRTXmJ7MCgYAHGvv4QjdmVl7uObqlG9DMGj1RjlAF0VxNf58q
NrLmVG2N2qV86wigg4wtZ6te4TdINfUcPkmQLYpLz8yx5Z2bsdq5OPP+CidoD5nC
WqnSTIKGR2uhQycjmLqL5a7WHaJsEFTqHh2wego1k+5kCUzC/KmvM7MKmkl6ICp+
3qZLUwKBgQCDOhKDwYo1hdiXoOOQqg/LZmpWOqjO3b4p99B9iJqhmXN0GKXIPSBh
5nqqmGsG8asSQhchs7EPMh8B80KbrDTeidWskZuUoQV27Al1UEmL6Zcl83qXD6sf
k9X9TwWyZtp5IL1CAEd/Il9ZTXFzr3lNaN8LCFnU+EIsz1YgUW8LTg==
-----END RSA PRIVATE KEY-----
`
)
func TestParseAddr(t *testing.T) {
// test hosts
expectedHost1 := "mydomain.com:1993"
expectedHost2 := "mydomain.com"
expectedHost3 := iris.DefaultServerHostname + ":9090"
expectedHost4 := "mydomain.com:443"
host1 := iris.ParseHost(expectedHost1)
host2 := iris.ParseHost(expectedHost2)
host3 := iris.ParseHost(":9090")
host4 := iris.ParseHost(expectedHost4)
if host1 != expectedHost1 {
t.Fatalf("Expecting server 1's host to be %s but we got %s", expectedHost1, host1)
}
if host2 != expectedHost2 {
t.Fatalf("Expecting server 2's host to be %s but we got %s", expectedHost2, host2)
}
if host3 != expectedHost3 {
t.Fatalf("Expecting server 3's host to be %s but we got %s", expectedHost3, host3)
}
if host4 != expectedHost4 {
t.Fatalf("Expecting server 4's host to be %s but we got %s", expectedHost4, host4)
}
// test hostname
expectedHostname1 := "mydomain.com"
expectedHostname2 := "mydomain.com"
expectedHostname3 := iris.DefaultServerHostname
expectedHostname4 := "mydomain.com"
hostname1 := iris.ParseHostname(host1)
hostname2 := iris.ParseHostname(host2)
hostname3 := iris.ParseHostname(host3)
hostname4 := iris.ParseHostname(host4)
if hostname1 != expectedHostname1 {
t.Fatalf("Expecting server 1's hostname to be %s but we got %s", expectedHostname1, hostname1)
}
if hostname2 != expectedHostname2 {
t.Fatalf("Expecting server 2's hostname to be %s but we got %s", expectedHostname2, hostname2)
}
if hostname3 != expectedHostname3 {
t.Fatalf("Expecting server 3's hostname to be %s but we got %s", expectedHostname3, hostname3)
}
if hostname4 != expectedHostname4 {
t.Fatalf("Expecting server 4's hostname to be %s but we got %s", expectedHostname4, hostname4)
}
// test scheme, no need to test fullhost(scheme+host)
expectedScheme1 := iris.SchemeHTTP
expectedScheme2 := iris.SchemeHTTP
expectedScheme3 := iris.SchemeHTTP
expectedScheme4 := iris.SchemeHTTPS
scheme1 := iris.ParseScheme(host1)
scheme2 := iris.ParseScheme(host2)
scheme3 := iris.ParseScheme(host3)
scheme4 := iris.ParseScheme(host4)
if scheme1 != expectedScheme1 {
t.Fatalf("Expecting server 1's hostname to be %s but we got %s", expectedScheme1, scheme1)
}
if scheme2 != expectedScheme2 {
t.Fatalf("Expecting server 2's hostname to be %s but we got %s", expectedScheme2, scheme2)
}
if scheme3 != expectedScheme3 {
t.Fatalf("Expecting server 3's hostname to be %s but we got %s", expectedScheme3, scheme3)
}
if scheme4 != expectedScheme4 {
t.Fatalf("Expecting server 4's hostname to be %s but we got %s", expectedScheme4, scheme4)
}
}
func getRandomNumber(min int, max int) int {
rand.Seed(time.Now().Unix())
return rand.Intn(max-min) + min
}
// works as
// defer listenTLS(iris.Default, hostTLS)()
func listenTLS(api *iris.Framework, hostTLS string) func() {
api.Close() // close any prev listener
api.Config.DisableBanner = true
// create the key and cert files on the fly, and delete them when this test finished
certFile, ferr := ioutil.TempFile("", "cert")
if ferr != nil {
api.Logger.Panic(ferr.Error())
}
keyFile, ferr := ioutil.TempFile("", "key")
if ferr != nil {
api.Logger.Panic(ferr.Error())
}
certFile.WriteString(testTLSCert)
keyFile.WriteString(testTLSKey)
go api.ListenTLS(hostTLS, certFile.Name(), keyFile.Name())
if ok := <-api.Available; !ok {
api.Logger.Panic("Unexpected error: server cannot start, please report this as bug!!")
}
return func() {
certFile.Close()
time.Sleep(50 * time.Millisecond)
os.Remove(certFile.Name())
keyFile.Close()
time.Sleep(50 * time.Millisecond)
os.Remove(keyFile.Name())
}
}
// Contains the server test for multi running servers
func TestMultiRunningServers_v1_PROXY(t *testing.T) {
api := iris.New()
host := "localhost"
hostTLS := host + ":" + strconv.Itoa(getRandomNumber(1919, 2221))
api.Get("/", func(ctx *iris.Context) {
ctx.Writef("Hello from %s", hostTLS)
})
// println("running main on: " + hostTLS)
defer listenTLS(api, hostTLS)()
e := httptest.New(api, t, httptest.ExplicitURL(true))
e.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
// proxy http to https
proxyHost := host + ":" + strconv.Itoa(getRandomNumber(3300, 3340))
// println("running proxy on: " + proxyHost)
iris.Proxy(proxyHost, "https://"+hostTLS)
// proxySrv := &http.Server{Addr: proxyHost, Handler: iris.ProxyHandler("https://" + hostTLS)}
// go proxySrv.ListenAndServe()
// time.Sleep(3 * time.Second)
eproxy := httptest.NewInsecure("http://"+proxyHost, t, httptest.ExplicitURL(true))
eproxy.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
}
// Contains the server test for multi running servers
func TestMultiRunningServers_v2(t *testing.T) {
api := iris.New()
domain := "localhost"
hostTLS := domain + ":" + strconv.Itoa(getRandomNumber(2222, 2229))
srv1Host := domain + ":" + strconv.Itoa(getRandomNumber(4446, 5444))
srv2Host := domain + ":" + strconv.Itoa(getRandomNumber(7778, 8887))
api.Get("/", func(ctx *iris.Context) {
ctx.Writef("Hello from %s", hostTLS)
})
defer listenTLS(api, hostTLS)()
// using the same iris' handler but not as proxy, just the same handler
srv2 := &http.Server{Handler: api.Router, Addr: srv2Host}
go srv2.ListenAndServe()
// using the proxy handler
srv1 := &http.Server{Handler: iris.ProxyHandler("https://" + hostTLS), Addr: srv1Host}
go srv1.ListenAndServe()
time.Sleep(500 * time.Millisecond) // wait a little for the http servers
e := httptest.New(api, t, httptest.ExplicitURL(true))
e.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
eproxy1 := httptest.NewInsecure("http://"+srv1Host, t, httptest.ExplicitURL(true))
eproxy1.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
eproxy2 := httptest.NewInsecure("http://"+srv2Host, t)
eproxy2.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
}
const (
testEnableSubdomain = true
testSubdomain = "mysubdomain"
)
func testSubdomainHost() string {
s := testSubdomain + "." + iris.Default.Config.VHost
return s
}
func testSubdomainURL() string {
subdomainHost := testSubdomainHost()
return iris.Default.Config.VScheme + subdomainHost
}
func subdomainTester(e *httpexpect.Expect) *httpexpect.Expect {
es := e.Builder(func(req *httpexpect.Request) {
req.WithURL(testSubdomainURL())
})
return es
}
type param struct {
Key string
Value string
}
type testRoute struct {
Method string
Path string
RequestPath string
RequestQuery string
Body string
Status int
Register bool
Params []param
URLParams []param
}
func TestMuxSimple(t *testing.T) {
testRoutes := []testRoute{
// FOUND - registered
{"GET", "/test_get", "/test_get", "", "hello, get!", 200, true, nil, nil},
{"POST", "/test_post", "/test_post", "", "hello, post!", 200, true, nil, nil},
{"PUT", "/test_put", "/test_put", "", "hello, put!", 200, true, nil, nil},
{"DELETE", "/test_delete", "/test_delete", "", "hello, delete!", 200, true, nil, nil},
{"HEAD", "/test_head", "/test_head", "", "hello, head!", 200, true, nil, nil},
{"OPTIONS", "/test_options", "/test_options", "", "hello, options!", 200, true, nil, nil},
{"CONNECT", "/test_connect", "/test_connect", "", "hello, connect!", 200, true, nil, nil},
{"PATCH", "/test_patch", "/test_patch", "", "hello, patch!", 200, true, nil, nil},
{"TRACE", "/test_trace", "/test_trace", "", "hello, trace!", 200, true, nil, nil},
// NOT FOUND - not registered
{"GET", "/test_get_nofound", "/test_get_nofound", "", "Not Found", 404, false, nil, nil},
{"POST", "/test_post_nofound", "/test_post_nofound", "", "Not Found", 404, false, nil, nil},
{"PUT", "/test_put_nofound", "/test_put_nofound", "", "Not Found", 404, false, nil, nil},
{"DELETE", "/test_delete_nofound", "/test_delete_nofound", "", "Not Found", 404, false, nil, nil},
{"HEAD", "/test_head_nofound", "/test_head_nofound", "", "Not Found", 404, false, nil, nil},
{"OPTIONS", "/test_options_nofound", "/test_options_nofound", "", "Not Found", 404, false, nil, nil},
{"CONNECT", "/test_connect_nofound", "/test_connect_nofound", "", "Not Found", 404, false, nil, nil},
{"PATCH", "/test_patch_nofound", "/test_patch_nofound", "", "Not Found", 404, false, nil, nil},
{"TRACE", "/test_trace_nofound", "/test_trace_nofound", "", "Not Found", 404, false, nil, nil},
// Parameters
{"GET", "/test_get_parameter1/:name", "/test_get_parameter1/iris", "", "name=iris", 200, true, []param{{"name", "iris"}}, nil},
{"GET", "/test_get_parameter2/:name/details/:something", "/test_get_parameter2/iris/details/anything", "", "name=iris,something=anything", 200, true, []param{{"name", "iris"}, {"something", "anything"}}, nil},
{"GET", "/test_get_parameter2/:name/details/:something/*else", "/test_get_parameter2/iris/details/anything/elsehere", "", "name=iris,something=anything,else=/elsehere", 200, true, []param{{"name", "iris"}, {"something", "anything"}, {"else", "elsehere"}}, nil},
// URL Parameters
{"GET", "/test_get_urlparameter1/first", "/test_get_urlparameter1/first", "name=irisurl", "name=irisurl", 200, true, nil, []param{{"name", "irisurl"}}},
{"GET", "/test_get_urlparameter2/second", "/test_get_urlparameter2/second", "name=irisurl&something=anything", "name=irisurl,something=anything", 200, true, nil, []param{{"name", "irisurl"}, {"something", "anything"}}},
{"GET", "/test_get_urlparameter2/first/second/third", "/test_get_urlparameter2/first/second/third", "name=irisurl&something=anything&else=elsehere", "name=irisurl,something=anything,else=elsehere", 200, true, nil, []param{{"name", "irisurl"}, {"something", "anything"}, {"else", "elsehere"}}},
}
iris.ResetDefault()
for idx := range testRoutes {
r := testRoutes[idx]
if r.Register {
iris.HandleFunc(r.Method, r.Path, func(ctx *iris.Context) {
ctx.SetStatusCode(r.Status)
if r.Params != nil && len(r.Params) > 0 {
ctx.WriteString(ctx.ParamsSentence())
} else if r.URLParams != nil && len(r.URLParams) > 0 {
if len(r.URLParams) != len(ctx.URLParams()) {
t.Fatalf("Error when comparing length of url parameters %d != %d", len(r.URLParams), len(ctx.URLParams()))
}
paramsKeyVal := ""
for idxp, p := range r.URLParams {
val := ctx.URLParam(p.Key)
paramsKeyVal += p.Key + "=" + val + ","
if idxp == len(r.URLParams)-1 {
paramsKeyVal = paramsKeyVal[0 : len(paramsKeyVal)-1]
}
}
ctx.WriteString(paramsKeyVal)
} else {
ctx.WriteString(r.Body)
}
})
}
}
e := httptest.New(iris.Default, t, httptest.Debug(true))
// run the tests (1)
for idx := range testRoutes {
r := testRoutes[idx]
e.Request(r.Method, r.RequestPath).WithQueryString(r.RequestQuery).
Expect().
Status(r.Status).Body().Equal(r.Body)
}
}
func TestMuxSimpleParty(t *testing.T) {
iris.ResetDefault()
h := func(c *iris.Context) { c.WriteString(c.Request.URL.Host + c.Request.RequestURI) }
if testEnableSubdomain {
subdomainParty := iris.Party(testSubdomain + ".")
{
subdomainParty.Get("/", h)
subdomainParty.Get("/path1", h)
subdomainParty.Get("/path2", h)
subdomainParty.Get("/namedpath/:param1/something/:param2", h)
subdomainParty.Get("/namedpath/:param1/something/:param2/else", h)
}
}
// simple
p := iris.Party("/party1")
{
p.Get("/", h)
p.Get("/path1", h)
p.Get("/path2", h)
p.Get("/namedpath/:param1/something/:param2", h)
p.Get("/namedpath/:param1/something/:param2/else", h)
}
iris.Default.Config.VHost = "0.0.0.0:" + strconv.Itoa(getRandomNumber(2222, 2399))
// iris.Default.Config.Tester.Debug = true
// iris.Default.Config.Tester.ExplicitURL = true
e := httptest.New(iris.Default, t)
request := func(reqPath string) {
e.Request("GET", reqPath).
Expect().
Status(iris.StatusOK).Body().Equal(iris.Default.Config.VHost + 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")
if testEnableSubdomain {
es := subdomainTester(e)
subdomainRequest := func(reqPath string) {
es.Request("GET", reqPath).
Expect().
Status(iris.StatusOK).Body().Equal(testSubdomainHost() + reqPath)
}
subdomainRequest("/")
subdomainRequest("/path1")
subdomainRequest("/path2")
subdomainRequest("/namedpath/theparam1/something/theparam2")
subdomainRequest("/namedpath/theparam1/something/theparam2/else")
}
}
// TestRealSubdomainSimple exists because the local examples some times passed but...
// hope that travis will not has problem with this
func TestRealSubdomainSimple(t *testing.T) {
api := iris.New()
host := "localhost:" + strconv.Itoa(getRandomNumber(4732, 4958))
subdomain := "admin"
subdomainHost := subdomain + "." + host
// no order, you can register subdomains at the end also.
admin := api.Party(subdomain + ".")
{
// admin.mydomain.com
admin.Get("/", func(c *iris.Context) {
c.Writef("INDEX FROM %s", subdomainHost)
})
// admin.mydomain.com/hey
admin.Get("/hey", func(c *iris.Context) {
c.Writef(subdomainHost + c.Request.RequestURI)
})
// admin.mydomain.com/hey2
admin.Get("/hey2", func(c *iris.Context) {
c.Writef(subdomainHost + c.Request.RequestURI)
})
}
// mydomain.com/
api.Get("/", func(c *iris.Context) {
c.Writef("INDEX FROM no-subdomain hey")
})
// mydomain.com/hey
api.Get("/hey", func(c *iris.Context) {
c.Writef("HEY FROM no-subdomain hey")
})
api.Config.DisableBanner = true
go api.Listen(host)
<-api.Available
e := httptest.New(api, t, httptest.ExplicitURL(true))
e.GET("/").Expect().Status(iris.StatusOK).Body().Equal("INDEX FROM no-subdomain hey")
e.GET("/hey").Expect().Status(iris.StatusOK).Body().Equal("HEY FROM no-subdomain hey")
sub := e.Builder(func(req *httpexpect.Request) {
req.WithURL("http://admin." + host)
})
sub.GET("/").Expect().Status(iris.StatusOK).Body().Equal("INDEX FROM " + subdomainHost)
sub.GET("/hey").Expect().Status(iris.StatusOK).Body().Equal(subdomainHost + "/hey")
sub.GET("/hey2").Expect().Status(iris.StatusOK).Body().Equal(subdomainHost + "/hey2")
}
func TestMuxPathEscape(t *testing.T) {
iris.ResetDefault()
iris.Config.EnablePathEscape = true
iris.Get("/details/:name", func(ctx *iris.Context) {
name := ctx.ParamDecoded("name")
highlight := ctx.URLParam("highlight")
ctx.Text(iris.StatusOK, fmt.Sprintf("name=%s,highlight=%s", name, highlight))
})
e := httptest.New(iris.Default, t)
e.GET("/details/Sakamoto desu ga").
WithQuery("highlight", "text").
Expect().Status(iris.StatusOK).Body().Equal("name=Sakamoto desu ga,highlight=text")
}
func TestMuxDecodeURL(t *testing.T) {
iris.ResetDefault()
iris.Get("/encoding/:url", func(ctx *iris.Context) {
url := ctx.ParamDecoded("url")
ctx.SetStatusCode(iris.StatusOK)
ctx.WriteString(url)
})
e := httptest.New(iris.Default, t)
e.GET("/encoding/http%3A%2F%2Fsome-url.com").Expect().Status(iris.StatusOK).Body().Equal("http://some-url.com")
}
func TestMuxCustomErrors(t *testing.T) {
var (
notFoundMessage = "Iris custom message for 404 not found"
internalServerMessage = "Iris custom message for 500 internal server error"
testRoutesCustomErrors = []testRoute{
// NOT FOUND CUSTOM ERRORS - not registered
{"GET", "/test_get_nofound_custom", "/test_get_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"POST", "/test_post_nofound_custom", "/test_post_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"PUT", "/test_put_nofound_custom", "/test_put_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"DELETE", "/test_delete_nofound_custom", "/test_delete_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"HEAD", "/test_head_nofound_custom", "/test_head_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"OPTIONS", "/test_options_nofound_custom", "/test_options_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"CONNECT", "/test_connect_nofound_custom", "/test_connect_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"PATCH", "/test_patch_nofound_custom", "/test_patch_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
{"TRACE", "/test_trace_nofound_custom", "/test_trace_nofound_custom", "", notFoundMessage, 404, false, nil, nil},
// SERVER INTERNAL ERROR 500 PANIC CUSTOM ERRORS - registered
{"GET", "/test_get_panic_custom", "/test_get_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"POST", "/test_post_panic_custom", "/test_post_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"PUT", "/test_put_panic_custom", "/test_put_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"DELETE", "/test_delete_panic_custom", "/test_delete_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"HEAD", "/test_head_panic_custom", "/test_head_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"OPTIONS", "/test_options_panic_custom", "/test_options_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"CONNECT", "/test_connect_panic_custom", "/test_connect_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"PATCH", "/test_patch_panic_custom", "/test_patch_panic_custom", "", internalServerMessage, 500, true, nil, nil},
{"TRACE", "/test_trace_panic_custom", "/test_trace_panic_custom", "", internalServerMessage, 500, true, nil, nil},
}
)
iris.ResetDefault()
// first register the testRoutes needed
for _, r := range testRoutesCustomErrors {
if r.Register {
iris.HandleFunc(r.Method, r.Path, func(ctx *iris.Context) {
ctx.EmitError(r.Status)
})
}
}
// register the custom errors
iris.OnError(iris.StatusNotFound, func(ctx *iris.Context) {
ctx.Writef("%s", notFoundMessage)
})
iris.OnError(iris.StatusInternalServerError, func(ctx *iris.Context) {
ctx.Writef("%s", internalServerMessage)
})
// create httpexpect instance that will call fasthtpp.RequestHandler directly
e := httptest.New(iris.Default, t)
// run the tests
for _, r := range testRoutesCustomErrors {
e.Request(r.Method, r.RequestPath).
Expect().
Status(r.Status).Body().Equal(r.Body)
}
}
type testUserAPI struct {
*iris.Context
}
// GET /users
func (u testUserAPI) Get() {
u.WriteString("Get Users\n")
}
// GET /users/:param1 which its value passed to the id argument
func (u testUserAPI) GetBy(id string) { // id equals to u.Param("param1")
u.Writef("Get By %s\n", id)
}
// PUT /users
func (u testUserAPI) Put() {
u.Writef("Put, name: %s\n", u.FormValue("name"))
}
// POST /users/:param1
func (u testUserAPI) PostBy(id string) {
u.Writef("Post By %s, name: %s\n", id, u.FormValue("name"))
}
// DELETE /users/:param1
func (u testUserAPI) DeleteBy(id string) {
u.Writef("Delete By %s\n", id)
}
func TestMuxAPI(t *testing.T) {
iris.ResetDefault()
middlewareResponseText := "I assume that you are authenticated\n"
h := []iris.HandlerFunc{func(ctx *iris.Context) { // optional middleware for .API
// do your work here, or render a login window if not logged in, get the user and send it to the next middleware, or do all here
ctx.Set("user", "username")
ctx.Next()
}, func(ctx *iris.Context) {
if ctx.Get("user") == "username" {
ctx.WriteString(middlewareResponseText)
ctx.Next()
} else {
ctx.SetStatusCode(iris.StatusUnauthorized)
}
}}
iris.API("/users", testUserAPI{}, h...)
// test a simple .Party with combination of .API
iris.Party("sites/:site").API("/users", testUserAPI{}, h...)
e := httptest.New(iris.Default, t)
siteID := "1"
apiPath := "/sites/" + siteID + "/users"
userID := "4077"
formname := "kataras"
// .API
e.GET("/users").Expect().Status(iris.StatusOK).Body().Equal(middlewareResponseText + "Get Users\n")
e.GET("/users/" + userID).Expect().Status(iris.StatusOK).Body().Equal(middlewareResponseText + "Get By " + userID + "\n")
e.PUT("/users").WithFormField("name", formname).Expect().Status(iris.StatusOK).Body().Equal(middlewareResponseText + "Put, name: " + formname + "\n")
e.POST("/users/"+userID).WithFormField("name", formname).Expect().Status(iris.StatusOK).Body().Equal(middlewareResponseText + "Post By " + userID + ", name: " + formname + "\n")
e.DELETE("/users/" + userID).Expect().Status(iris.StatusOK).Body().Equal(middlewareResponseText + "Delete By " + userID + "\n")
// .Party
e.GET(apiPath).Expect().Status(iris.StatusOK).Body().Equal(middlewareResponseText + "Get Users\n")
e.GET(apiPath + "/" + userID).Expect().Status(iris.StatusOK).Body().Equal(middlewareResponseText + "Get By " + userID + "\n")
e.PUT(apiPath).WithFormField("name", formname).Expect().Status(iris.StatusOK).Body().Equal(middlewareResponseText + "Put, name: " + formname + "\n")
e.POST(apiPath+"/"+userID).WithFormField("name", formname).Expect().Status(iris.StatusOK).Body().Equal(middlewareResponseText + "Post By " + userID + ", name: " + formname + "\n")
e.DELETE(apiPath + "/" + userID).Expect().Status(iris.StatusOK).Body().Equal(middlewareResponseText + "Delete By " + userID + "\n")
}
type myTestHandlerData struct {
Sysname string // this will be the same for all requests
Version int // this will be the same for all requests
DynamicPathParameter string // this will be different for each request
}
type myTestCustomHandler struct {
data myTestHandlerData
}
func (m *myTestCustomHandler) Serve(ctx *iris.Context) {
data := &m.data
data.DynamicPathParameter = ctx.Param("myparam")
ctx.JSON(iris.StatusOK, data)
}
func TestMuxCustomHandler(t *testing.T) {
iris.ResetDefault()
myData := myTestHandlerData{
Sysname: "Redhat",
Version: 1,
}
iris.Handle("GET", "/custom_handler_1/:myparam", &myTestCustomHandler{myData})
iris.Handle("GET", "/custom_handler_2/:myparam", &myTestCustomHandler{myData})
e := httptest.New(iris.Default, t)
// two times per testRoute
param1 := "thisimyparam1"
expectedData1 := myData
expectedData1.DynamicPathParameter = param1
e.GET("/custom_handler_1/" + param1).Expect().Status(iris.StatusOK).JSON().Equal(expectedData1)
param2 := "thisimyparam2"
expectedData2 := myData
expectedData2.DynamicPathParameter = param2
e.GET("/custom_handler_1/" + param2).Expect().Status(iris.StatusOK).JSON().Equal(expectedData2)
param3 := "thisimyparam3"
expectedData3 := myData
expectedData3.DynamicPathParameter = param3
e.GET("/custom_handler_2/" + param3).Expect().Status(iris.StatusOK).JSON().Equal(expectedData3)
param4 := "thisimyparam4"
expectedData4 := myData
expectedData4.DynamicPathParameter = param4
e.GET("/custom_handler_2/" + param4).Expect().Status(iris.StatusOK).JSON().Equal(expectedData4)
}
func TestMuxFireMethodNotAllowed(t *testing.T) {
iris.ResetDefault()
iris.Default.Config.FireMethodNotAllowed = true
h := func(ctx *iris.Context) {
ctx.WriteString(ctx.Method())
}
iris.Default.OnError(iris.StatusMethodNotAllowed, func(ctx *iris.Context) {
ctx.WriteString("Hello from my custom 405 page")
})
iris.Get("/mypath", h)
iris.Put("/mypath", h)
e := httptest.New(iris.Default, t)
e.GET("/mypath").Expect().Status(iris.StatusOK).Body().Equal("GET")
e.PUT("/mypath").Expect().Status(iris.StatusOK).Body().Equal("PUT")
// this should fail with 405 and catch by the custom http error
e.POST("/mypath").Expect().Status(iris.StatusMethodNotAllowed).Body().Equal("Hello from my custom 405 page")
iris.Close()
}
func TestRedirectHTTPS(t *testing.T) {
api := iris.New(iris.OptionIsDevelopment(true))
host := "localhost:" + strconv.Itoa(getRandomNumber(1717, 9281))
expectedBody := "Redirected to /redirected"
api.Get("/redirect", func(ctx *iris.Context) { ctx.Redirect("/redirected") })
api.Get("/redirected", func(ctx *iris.Context) { ctx.Text(iris.StatusOK, "Redirected to "+ctx.Path()) })
defer listenTLS(api, host)()
e := httptest.New(api, t)
e.GET("/redirect").Expect().Status(iris.StatusOK).Body().Equal(expectedBody)
}
func TestRouteStateSimple(t *testing.T) {
iris.ResetDefault()
offlineRoutePath := "/api/user/:userid"
offlineRouteRequestedTestPath := "/api/user/42"
offlineBody := "user with id: 42"
offlineRoute := iris.None(offlineRoutePath, func(ctx *iris.Context) {
userid := ctx.Param("userid")
if userid != "42" {
// we are expecting userid 42 always in this test so
t.Fatalf("what happened? expected userid to be 42 but got %s", userid)
}
ctx.Writef(offlineBody)
})("api.users") // or an empty (), required, in order to get the Route instance.
// change the "user.api" state from offline to online and online to offline
iris.Get("/change", func(ctx *iris.Context) {
// here
if offlineRoute.IsOnline() {
// set to offline
iris.SetRouteOffline(offlineRoute)
} else {
// set to online if it was not online(so it was offline)
iris.SetRouteOnline(offlineRoute, iris.MethodGet)
}
})
iris.Get("/execute", func(ctx *iris.Context) {
// here
ctx.ExecRouteAgainst(offlineRoute, "/api/user/42")
})
hello := "Hello from index"
iris.Get("/", func(ctx *iris.Context) {
ctx.Writef(hello)
})
e := httptest.New(iris.Default, t)
e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(hello)
// here
// the status should be not found, the route is invisible from outside world
e.GET(offlineRouteRequestedTestPath).Expect().Status(iris.StatusNotFound)
// set the route online with the /change
e.GET("/change").Expect().Status(iris.StatusOK)
// try again, it should be online now
e.GET(offlineRouteRequestedTestPath).Expect().Status(iris.StatusOK).Body().Equal(offlineBody)
// change to offline again
e.GET("/change").Expect().Status(iris.StatusOK)
// and test again, it should be offline now
e.GET(offlineRouteRequestedTestPath).Expect().Status(iris.StatusNotFound)
// finally test the execute on the offline route
// it should be remains offline but execute the route like it is from client request.
e.GET("/execute").Expect().Status(iris.StatusOK).Body().Equal(offlineBody)
e.GET(offlineRouteRequestedTestPath).Expect().Status(iris.StatusNotFound)
}

View File

@ -5,8 +5,8 @@ import (
"net/http"
"testing"
"github.com/gavv/httpexpect"
"github.com/kataras/iris"
"github.com/iris-contrib/httpexpect"
"gopkg.in/kataras/iris.v6"
)
type (
@ -66,7 +66,7 @@ func DefaultConfiguration() *Configuration {
// New Prepares and returns a new test framework based on the api
// is useful when you need to have more than one test framework for the same iris instance
// usage:
// iris.Get("/mypath", func(ctx *iris.Context){ctx.Write("my body")})
// iris.Default.Get("/mypath", func(ctx *iris.Context){ctx.Write("my body")})
// ...
// e := httptest.New(iris.Default, t)
// e.GET("/mypath").Expect().Status(iris.StatusOK).Body().Equal("my body")
@ -79,11 +79,10 @@ func New(api *iris.Framework, t *testing.T, setters ...OptionSetter) *httpexpect
}
api.Set(iris.OptionDisableBanner(true))
api.Adapt(iris.DevLogger())
baseURL := ""
if !api.Plugins.PreBuildFired() {
api.Build()
}
api.Boot()
if !conf.ExplicitURL {
baseURL = api.Config.VScheme + api.Config.VHost
// if it's still empty then set it to the default server addr
@ -96,7 +95,7 @@ func New(api *iris.Framework, t *testing.T, setters ...OptionSetter) *httpexpect
testConfiguration := httpexpect.Config{
BaseURL: baseURL,
Client: &http.Client{
Transport: httpexpect.NewBinder(api.Router),
Transport: httpexpect.NewBinder(api),
Jar: httpexpect.NewJar(),
},
Reporter: httpexpect.NewAssertReporter(t),

2412
iris.go

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
package main // import "github.com/kataras/iris/iris"
package main
/*

View File

@ -2,7 +2,7 @@ package main
import (
"github.com/kataras/cli"
"github.com/kataras/iris"
"gopkg.in/kataras/iris.v6"
)
var (

View File

@ -1,84 +0,0 @@
# Middleware
We should mention that Iris is compatible with **ALL** net/http middleware out there,
You are not restricted to so-called 'iris-made' middleware. They do exists, mostly, for your learning curve.
Navigate through [iris-contrib/middleware](https://github.com/iris-contrib/through) repository to view iris-made 'middleware'.
> By the word 'middleware', we mean a single or a collection of route handlers which may execute before/or after the main route handler.
## Installation
```sh
$ go get github.com/iris-contrib/middleware/...
```
## How can I register a middleware?
```go
app := iris.New()
/* per root path and all its children */
app.Use(logger)
/* execute always last */
// app.Done(logger)
/* per-route, order matters. */
// app.Get("/", logger, indexHandler)
/* per party (group of routes) */
// userRoutes := app.Party("/user", logger)
// userRoutes.Post("/login", loginAuthHandler)
```
## How 'hard' is to create an Iris middleware?
```go
myMiddleware := func(ctx *iris.Context){
/* using ctx.Set you can transfer ANY data between handlers,
use ctx.Get("welcomed") to get its value on the next handler(s).
*/
ctx.Set("welcomed", true)
println("My middleware!")
}
```
> func(ctx *iris.Context) is just the `iris.HandlerFunc` signature which implements the `iris.Handler`/ `Serve(Context)` method.
```go
app := iris.New()
/* root path and all its children */
app.UseFunc(myMiddleware)
app.Get("/", indexHandler)
```
## Convert `http.Handler` to `iris.Handler` using the `iris.ToHandler`
```go
// ToHandler converts different type styles of handlers that you
// used to use (usually with third-party net/http middleware) to an iris.HandlerFunc.
//
// Supported types:
// - .ToHandler(h http.Handler)
// - .ToHandler(func(w http.ResponseWriter, r *http.Request))
// - .ToHandler(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc))
func ToHandler(handler interface{}) HandlerFunc
```
```go
app := iris.New()
sillyHTTPHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
println(r.RequestURI)
})
app.Use(iris.ToHandler(sillyHTTPHandler))
```
## What next?
Read more about [iris.Handler](https://docs.iris-go.com/using-handlers.html), [iris.HandlerFunc](https://docs.iris-go.com/using-handlerfuncs.html) and [Middleware](https://docs.iris-go.com/middleware.html).

View File

@ -0,0 +1,56 @@
package main
import (
"time"
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/middleware/basicauth"
)
func main() {
app := iris.New()
app.Adapt(httprouter.New()) // adapt a router first of all
authConfig := basicauth.Config{
Users: map[string]string{"myusername": "mypassword", "mySecondusername": "mySecondpassword"},
Realm: "Authorization Required", // defaults to "Authorization Required"
ContextKey: "mycustomkey", // defaults to "user"
Expires: time.Duration(30) * time.Minute,
}
authentication := basicauth.New(authConfig)
app.Get("/", func(ctx *iris.Context) { ctx.Redirect("/admin") })
// to global app.Use(authentication) (or app.UseGlobal before the .Listen)
// to routes
/*
app.Get("/mysecret", authentication, func(ctx *iris.Context) {
username := ctx.GetString("mycustomkey") // the Contextkey from the authConfig
ctx.Writef("Hello authenticated user: %s ", username)
})
*/
// to party
needAuth := app.Party("/admin", authentication)
{
//http://localhost:8080/admin
needAuth.Get("/", func(ctx *iris.Context) {
username := ctx.GetString("mycustomkey") // the Contextkey from the authConfig
ctx.Writef("Hello authenticated user: %s from: %s ", username, ctx.Path())
})
// http://localhost:8080/admin/profile
needAuth.Get("/profile", func(ctx *iris.Context) {
username := ctx.GetString("mycustomkey") // the Contextkey from the authConfig
ctx.Writef("Hello authenticated user: %s from: %s ", username, ctx.Path())
})
// http://localhost:8080/admin/settings
needAuth.Get("/settings", func(ctx *iris.Context) {
username := authConfig.User(ctx) // shortcut for ctx.GetString("mycustomkey")
ctx.Writef("Hello authenticated user: %s from: %s ", username, ctx.Path())
})
}
// open http://localhost:8080/admin
app.Listen(":8080")
}

View File

@ -0,0 +1,131 @@
package basicauth
import (
"encoding/base64"
"strconv"
"time"
"gopkg.in/kataras/iris.v6"
)
// +------------------------------------------------------------+
// | Middleware usage |
// +------------------------------------------------------------+
//
// import "gopkg.in/kataras/iris.v6/middleware/basicauth"
//
// app := iris.New()
// authentication := basicauth.Default(map[string]string{"myusername": "mypassword", "mySecondusername": "mySecondpassword"})
// app.Get("/dashboard", authentication, func(ctx *iris.Context){})
//
// for more configuration basicauth.New(basicauth.Config{...})
// see _example
type (
encodedUser struct {
HeaderValue string
Username string
logged bool
expires time.Time
}
encodedUsers []encodedUser
basicAuthMiddleware struct {
config Config
// these are filled from the config.Users map at the startup
auth encodedUsers
realmHeaderValue string
expireEnabled bool // if the config.Expires is a valid date, default disabled
}
)
//
// New takes one parameter, the Config returns a HandlerFunc
// use: iris.UseFunc(New(...)), iris.Get(...,New(...),...)
func New(c Config) iris.HandlerFunc {
b := &basicAuthMiddleware{config: DefaultConfig().MergeSingle(c)}
b.init()
return b.Serve
}
// Default takes one parameter, the users returns a HandlerFunc
// use: iris.UseFunc(Default(...)), iris.Get(...,Default(...),...)
func Default(users map[string]string) iris.HandlerFunc {
c := DefaultConfig()
c.Users = users
return New(c)
}
//
// User returns the user from context key same as 'ctx.GetString("user")' but cannot be used by the developer, use the basicauth.Config.User func instead.
func (b *basicAuthMiddleware) User(ctx *iris.Context) string {
return b.config.User(ctx)
}
func (b *basicAuthMiddleware) init() {
// pass the encoded users from the user's config's Users value
b.auth = make(encodedUsers, 0, len(b.config.Users))
for k, v := range b.config.Users {
fullUser := k + ":" + v
header := "Basic " + base64.StdEncoding.EncodeToString([]byte(fullUser))
b.auth = append(b.auth, encodedUser{HeaderValue: header, Username: k, logged: false, expires: DefaultExpireTime})
}
// set the auth realm header's value
b.realmHeaderValue = "Basic realm=" + strconv.Quote(b.config.Realm)
if b.config.Expires > 0 {
b.expireEnabled = true
}
}
func (b *basicAuthMiddleware) findAuth(headerValue string) (auth *encodedUser, found bool) {
if len(headerValue) == 0 {
return
}
for _, user := range b.auth {
if user.HeaderValue == headerValue {
auth = &user
found = true
break
}
}
return
}
func (b *basicAuthMiddleware) askForCredentials(ctx *iris.Context) {
ctx.SetHeader("WWW-Authenticate", b.realmHeaderValue)
ctx.SetStatusCode(iris.StatusUnauthorized)
}
// Serve the actual middleware
func (b *basicAuthMiddleware) Serve(ctx *iris.Context) {
if auth, found := b.findAuth(ctx.RequestHeader("Authorization")); !found {
b.askForCredentials(ctx)
// don't continue to the next handler
} else {
// all ok set the context's value in order to be getable from the next handler
ctx.Set(b.config.ContextKey, auth.Username)
if b.expireEnabled {
if auth.logged == false {
auth.expires = time.Now().Add(b.config.Expires)
auth.logged = true
}
if time.Now().After(auth.expires) {
b.askForCredentials(ctx) // ask for authentication again
return
}
}
ctx.Next() // continue
}
}

View File

@ -0,0 +1,48 @@
package basicauth
import (
"time"
"github.com/imdario/mergo"
"gopkg.in/kataras/iris.v6"
)
const (
// DefaultBasicAuthRealm is "Authorization Required"
DefaultBasicAuthRealm = "Authorization Required"
// DefaultBasicAuthContextKey is the "auth"
// this key is used to do context.Set("user", theUsernameFromBasicAuth)
DefaultBasicAuthContextKey = "user"
)
// DefaultExpireTime zero time
var DefaultExpireTime time.Time // 0001-01-01 00:00:00 +0000 UTC
// Config the configs for the basicauth middleware
type Config struct {
// Users a map of login and the value (username/password)
Users map[string]string
// Realm http://tools.ietf.org/html/rfc2617#section-1.2. Default is "Authorization Required"
Realm string
// ContextKey the key for ctx.GetString(...). Default is 'user'
ContextKey string
// Expires expiration duration, default is 0 never expires
Expires time.Duration
}
// DefaultConfig returns the default configs for the BasicAuth middleware
func DefaultConfig() Config {
return Config{make(map[string]string), DefaultBasicAuthRealm, DefaultBasicAuthContextKey, 0}
}
// MergeSingle merges the default with the given config and returns the result
func (c Config) MergeSingle(cfg Config) (config Config) {
config = cfg
mergo.Merge(&config, c)
return
}
// User returns the user from context key same as 'ctx.GetString("user")' but cannot be used by the developer, this is only here in order to understand how you can get the authenticated username
func (c Config) User(ctx *iris.Context) string {
return ctx.GetString(c.ContextKey)
}

167
middleware/i18n/LICENSE Normal file
View File

@ -0,0 +1,167 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

69
middleware/i18n/README.md Normal file
View File

@ -0,0 +1,69 @@
## Middleware information
This folder contains a middleware for internationalization uses a third-party package named i81n.
More can be found here:
[https://github.com/Unknwon/i18n](https://github.com/Unknwon/i18n)
## Install
```sh
$ go get -u github.com/iris-contrib/middleware/i18n
```
## Description
Package i18n is for app Internationalization and Localization.
## How to use
Create folder named 'locales'
```
///Files:
./locales/locale_en-US.ini
./locales/locale_el-US.ini
```
Contents on locale_en-US:
```
hi = hello, %s
```
Contents on locale_el-GR:
```
hi = <20><><EFBFBD><EFBFBD>, %s
```
```go
package main
import (
"github.com/kataras/iris"
"github.com/iris-contrib/middleware/i18n"
)
func main() {
iris.UseFunc(i18n.New(i18n.Config{Default: "en-US",
Languages: map[string]string{
"en-US": "./locales/locale_en-US.ini",
"el-GR": "./locales/locale_el-GR.ini",
"zh-CN": "./locales/locale_zh-CN.ini"}}))
// or iris.Use(i18n.I18nHandler(....))
// or iris.Get("/",i18n.I18n(....), func (ctx *iris.Context){})
iris.Get("/", func(ctx *iris.Context) {
hi := ctx.GetFmt("translate")("hi", "maki") // hi is the key, 'maki' is the %s, the second parameter is optional
language := ctx.Get("language") // language is the language key, example 'en-US'
ctx.Write("From the language %s translated output: %s", language, hi)
})
iris.Listen(":8080")
}
```
### [For a working example, click here](https://github.com/kataras/iris/tree/examples/middleware_internationalization_i18n)

View File

@ -0,0 +1 @@
hi = Γεια, %s

View File

@ -0,0 +1 @@
hi = hello, %s

View File

@ -0,0 +1 @@
hi = 您好,%s

View File

@ -0,0 +1,50 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/middleware/i18n"
)
func main() {
app := iris.New()
app.Adapt(httprouter.New()) // adapt a router first of all
app.Use(i18n.New(i18n.Config{
Default: "en-US",
URLParameter: "lang",
Languages: map[string]string{
"en-US": "./locales/locale_en-US.ini",
"el-GR": "./locales/locale_el-GR.ini",
"zh-CN": "./locales/locale_zh-CN.ini"}}))
app.Get("/", func(ctx *iris.Context) {
// it tries to find the language by:
// ctx.Get("language") , that should be setted on other middleware before the i18n middleware*
// if that was empty then
// it tries to find from the URLParameter setted on the configuration
// if not found then
// it tries to find the language by the "lang" cookie
// if didn't found then it it set to the Default setted on the configuration
// hi is the key, 'kataras' is the %s on the .ini file
// the second parameter is optional
// hi := ctx.Translate("hi", "kataras")
// or:
hi := i18n.Translate(ctx, "hi", "kataras")
language := ctx.Get(iris.TranslateLanguageContextKey) // language is the language key, example 'en-US'
// The first succeed language found saved at the cookie with name ("language"),
// you can change that by changing the value of the: iris.TranslateLanguageContextKey
ctx.Writef("From the language %s translated output: %s", language, hi)
})
// go to http://localhost:8080/?lang=el-GR
// or http://localhost:8080
// or http://localhost:8080/?lang=zh-CN
app.Listen(":8080")
}

18
middleware/i18n/config.go Normal file
View File

@ -0,0 +1,18 @@
package i18n
// Config the i18n options
type Config struct {
// Default set it if you want a default language
//
// Checked: Configuration state, not at runtime
Default string
// URLParameter is the name of the url parameter which the language can be indentified
//
// Checked: Serving state, runtime
URLParameter string
// Languages is a map[string]string which the key is the language i81n and the value is the file location
//
// Example of key is: 'en-US'
// Example of value is: './locales/en-US.ini'
Languages map[string]string
}

101
middleware/i18n/i18n.go Normal file
View File

@ -0,0 +1,101 @@
package i18n
import (
"reflect"
"strings"
"github.com/Unknwon/i18n"
"gopkg.in/kataras/iris.v6"
)
type i18nMiddleware struct {
config Config
}
// Serve serves the request, the actual middleware's job is here
func (i *i18nMiddleware) Serve(ctx *iris.Context) {
wasByCookie := false
language := i.config.Default
if ctx.GetString(iris.TranslateLanguageContextKey) == "" {
// try to get by url parameter
language = ctx.URLParam(i.config.URLParameter)
if language == "" {
// then try to take the lang field from the cookie
language = ctx.GetCookie(iris.TranslateLanguageContextKey)
if len(language) > 0 {
wasByCookie = true
} else {
// try to get by the request headers(?)
if langHeader := ctx.RequestHeader("Accept-Language"); i18n.IsExist(langHeader) {
language = langHeader
}
}
}
// if it was not taken by the cookie, then set the cookie in order to have it
if !wasByCookie {
ctx.SetCookieKV(iris.TranslateLanguageContextKey, language)
}
if language == "" {
language = i.config.Default
}
ctx.Set(iris.TranslateLanguageContextKey, language)
}
locale := i18n.Locale{Lang: language}
ctx.Set(iris.TranslateFunctionContextKey, locale.Tr)
ctx.Next()
}
// Translate returns the translated word from a context
// the second parameter is the key of the world or line inside the .ini file
// the third parameter is the '%s' of the world or line inside the .ini file
func Translate(ctx *iris.Context, format string, args ...interface{}) string {
return ctx.Translate(format, args...)
}
// New returns a new i18n middleware
func New(c Config) iris.HandlerFunc {
if len(c.Languages) == 0 {
panic("You cannot use this middleware without set the Languages option, please try again and read the _example.")
}
i := &i18nMiddleware{config: c}
firstlanguage := ""
//load the files
for k, v := range c.Languages {
if !strings.HasSuffix(v, ".ini") {
v += ".ini"
}
err := i18n.SetMessage(k, v)
if err != nil && err != i18n.ErrLangAlreadyExist {
panic("Iris i18n Middleware: Failed to set locale file" + k + " Error:" + err.Error())
}
if firstlanguage == "" {
firstlanguage = k
}
}
// if not default language setted then set to the first of the i.options.Languages
if c.Default == "" {
c.Default = firstlanguage
}
i18n.SetDefaultLang(i.config.Default)
return i.Serve
}
// TranslatedMap returns translated map[string]interface{} from i18n structure
func TranslatedMap(sourceInterface interface{}, ctx *iris.Context) map[string]interface{} {
iType := reflect.TypeOf(sourceInterface).Elem()
result := make(map[string]interface{})
for i := 0; i < iType.NumField(); i++ {
fieldName := reflect.TypeOf(sourceInterface).Elem().Field(i).Name
fieldValue := reflect.ValueOf(sourceInterface).Elem().Field(i).String()
result[fieldName] = Translate(ctx, fieldValue)
}
return result
}

View File

@ -0,0 +1,53 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/middleware/logger"
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger()) // it just enables the print of the iris.DevMode logs. Enable it to view the middleware's messages.
app.Adapt(httprouter.New())
customLogger := logger.New(logger.Config{
// Status displays status code
Status: true,
// IP displays request's remote address
IP: true,
// Method displays the http method
Method: true,
// Path displays the request path
Path: true,
})
app.Use(customLogger)
app.Get("/", func(ctx *iris.Context) {
ctx.Writef("hello")
})
app.Get("/1", func(ctx *iris.Context) {
ctx.Writef("hello")
})
app.Get("/2", func(ctx *iris.Context) {
ctx.Writef("hello")
})
// log http errors
errorLogger := logger.New()
app.OnError(iris.StatusNotFound, func(ctx *iris.Context) {
errorLogger.Serve(ctx)
ctx.Writef("My Custom 404 error page ")
})
// http://localhost:8080
// http://localhost:8080/1
// http://localhost:8080/2
app.Listen(":8080")
}

View File

@ -0,0 +1,21 @@
package logger
// Config are the options of the logger middlweare
// contains 4 bools
// Status, IP, Method, Path
// if set to true then these will print
type Config struct {
// Status displays status code (bool)
Status bool
// IP displays request's remote address (bool)
IP bool
// Method displays the http method (bool)
Method bool
// Path displays the request path (bool)
Path bool
}
// DefaultConfig returns an options which all properties are true except EnableColors
func DefaultConfig() Config {
return Config{true, true, true, true}
}

View File

@ -0,0 +1,62 @@
package logger
import (
"fmt"
"strconv"
"time"
"gopkg.in/kataras/iris.v6"
)
type loggerMiddleware struct {
config Config
}
// Serve serves the middleware
func (l *loggerMiddleware) Serve(ctx *iris.Context) {
//all except latency to string
var date, status, ip, method, path string
var latency time.Duration
var startTime, endTime time.Time
path = ctx.Path()
method = ctx.Method()
startTime = time.Now()
ctx.Next()
//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.config.Status {
status = strconv.Itoa(ctx.ResponseWriter.StatusCode())
}
if l.config.IP {
ip = ctx.RemoteAddr()
}
if !l.config.Method {
method = ""
}
if !l.config.Path {
path = ""
}
//finally print the logs
ctx.Log(iris.DevMode, fmt.Sprintf("%s %v %4v %s %s %s \n", date, status, latency, ip, method, path))
}
// New returns the logger middleware
// receives optional configs(logger.Config)
func New(cfg ...Config) iris.HandlerFunc {
c := DefaultConfig()
if len(cfg) > 0 {
c = cfg[0]
}
l := &loggerMiddleware{config: c}
return l.Serve
}

View File

@ -0,0 +1,31 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/middleware/recover"
)
func main() {
app := iris.New()
app.Adapt(httprouter.New())
app.Adapt(iris.DevLogger()) // fast way to enable non-fatal messages to be printed to the user
// (yes in iris even recover's errors are not fatal because it's restarting,
// ProdMode messages are only for things that Iris cannot continue at all,
// these are logged by-default but you can change that behavior too by passing a different LoggerPolicy to the .Adapt)
app.Use(recover.New()) // it's io.Writer is the same as app.Config.LoggerOut
i := 0
// let's simmilate a panic every next request
app.Get("/", func(ctx *iris.Context) {
i++
if i%2 == 0 {
panic("a panic here")
}
ctx.Writef("Hello, refresh one time more to get panic!")
})
// http://localhost:8080
app.Listen(":8080")
}

View File

@ -0,0 +1,58 @@
package recover
import (
"fmt"
"runtime"
"strconv"
"gopkg.in/kataras/iris.v6"
)
func getRequestLogs(ctx *iris.Context) string {
var status, ip, method, path string
status = strconv.Itoa(ctx.ResponseWriter.StatusCode())
path = ctx.Path()
method = ctx.Method()
ip = ctx.RemoteAddr()
// the date should be logged by iris' Logger, so we skip them
return fmt.Sprintf("%v %s %s %s", status, path, method, ip)
}
// New returns a new recover middleware
// it logs to the LoggerOut iris' configuration field if its IsDeveloper configuration field is enabled.
// otherwise it just continues to serve
func New() iris.HandlerFunc {
return func(ctx *iris.Context) {
defer func() {
if err := recover(); err != nil {
if ctx.IsStopped() {
return
}
var stacktrace string
for i := 1; ; i++ {
_, f, l, got := runtime.Caller(i)
if !got {
break
}
stacktrace += fmt.Sprintf("%s:%d\n", f, l)
}
// when stack finishes
logMessage := fmt.Sprintf("Recovered from a route's Handler('%s')\n", ctx.GetHandlerName())
logMessage += fmt.Sprintf("At Request: %s\n", getRequestLogs(ctx))
logMessage += fmt.Sprintf("Trace: %s\n", err)
logMessage += fmt.Sprintf("\n%s\n", stacktrace)
ctx.Log(iris.DevMode, logMessage)
ctx.StopExecution()
ctx.EmitError(iris.StatusInternalServerError)
}
}()
ctx.Next()
}
}

615
plugin.go
View File

@ -1,615 +0,0 @@
package iris
///TODO: Be ready for go v1.8 in order to accomplish my first idea.
import (
"log"
"sync"
"github.com/kataras/go-errors"
"github.com/kataras/go-fs"
)
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 registered yet, you cannot remove a plugin from an empty list!'
errPluginRemoveNoPlugins = errors.New("No plugins are registered 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 (
// 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 {
}
// 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
}
// pluginGetDescription implements the GetDescription() string method
pluginGetDescription interface {
// GetDescription has to returns the description of what the plugins is used for
GetDescription() string
}
// 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(PluginContainer) error
}
// pluginPreLookup implements the PreRoute(Route) method
pluginPreLookup interface {
// PreLookup called before register a route
PreLookup(Route)
}
// PreLookupFunc implements the simple function listener for the PreLookup(Route)
PreLookupFunc func(Route)
// pluginPreBuild implements the PreBuild(*Framework) method
pluginPreBuild interface {
// PreBuild it's being called once time, BEFORE the Server is started and before PreListen
// is used to do work before all other things are ready
// use this event if you want to add routes to your iris station
// or make any changes to the iris main configuration
// receiver is the station
PreBuild(*Framework)
}
// PreBuildFunc implements the simple function listener for the PreBuild(*Framework)
PreBuildFunc func(*Framework)
// 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
// receiver is the station
PreListen(*Framework)
}
// 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(*Framework)
}
// PostListenFunc implements the simple function listener for the PostListen(*Framework)
PostListenFunc func(*Framework)
// pluginPostInterrupt implements the PostInterrupt(*Framework) method
pluginPostInterrupt interface {
// PostInterrupt it's being called only one time, when os.Interrupt system event catched
// graceful shutdown can be done here
//
// Read more here: https://github.com/kataras/iris/blob/master/HISTORY.md#608---609
// Example: https://github.com/iris-contrib/examples/tree/master/os_interrupt
PostInterrupt(*Framework)
}
// PostInterruptFunc implements the simple function listener for the PostInterrupt(*Framework)
//
// Read more here: https://github.com/kataras/iris/blob/master/HISTORY.md#608---609
// Example: https://github.com/iris-contrib/examples/tree/master/os_interrupt
PostInterruptFunc 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(*Framework)
}
// PreCloseFunc implements the simple function listener for the PreClose(*Framework)
PreCloseFunc func(*Framework)
// 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.
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 Plugin, downloadURL string) // bool
}
// PreDownloadFunc implements the simple function listener for the PreDownload(plugin,string)
PreDownloadFunc func(Plugin, string)
// PluginContainer is the interface which the pluginContainer should implements
PluginContainer interface {
Add(...Plugin) error
Remove(string) error
Len() int
GetName(Plugin) string
GetDescription(Plugin) string
GetByName(string) Plugin
Printf(string, ...interface{})
Fired(string) int
PreLookup(PreLookupFunc)
DoPreLookup(Route)
PreLookupFired() bool
PreBuild(PreBuildFunc)
DoPreBuild(*Framework)
PreBuildFired() bool
PreListen(PreListenFunc)
DoPreListen(*Framework)
DoPreListenParallel(*Framework)
PreListenFired() bool
PostListen(PostListenFunc)
DoPostListen(*Framework)
PostListenFired() bool
PostInterrupt(PostInterruptFunc)
DoPostInterrupt(*Framework)
PostInterruptFired() bool
PreClose(PreCloseFunc)
DoPreClose(*Framework)
PreCloseFired() bool
PreDownload(PreDownloadFunc)
DoPreDownload(Plugin, string)
PreDownloadFired() bool
//
GetAll() []Plugin
// GetDownloader is the only one module that is used and fire listeners at the same time in this file
GetDownloader() PluginDownloadManager
} //Note: custom event callbacks, never used internaly by Iris, but if you need them use this: github.com/kataras/go-events
// 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
//
// first parameter is the remote url file zip
// second parameter is the target directory
// returns a string(installedDirectory) and an error
//
// (string) installedDirectory is the directory which the zip file had, this is the real installation path, you don't need to know what it's because these things maybe change to the future let's keep it to return the correct path.
// the installedDirectory is not empty when the installation is succed, the targetDirectory is not already exists and no error happens
// the installedDirectory is empty when the installation is already done by previous time or an error happens
Install(remoteFileZip string, targetDirectory string) (string, error)
}
// 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 plugin
// PreLookup called before register a route
func (fn PreLookupFunc) PreLookup(r Route) {
fn(r)
}
// PreBuild it's being called once time, BEFORE the Server is started and before PreListen
// is used to do work before all other things are ready
// use this event if you want to add routes to your iris station
// or make any changes to the iris main configuration
// receiver is the station
func (fn PreBuildFunc) PreBuild(station *Framework) {
fn(station)
}
// 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 *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 *Framework) {
fn(station)
}
// PostInterrupt it's being called only one time, when os.Interrupt system event catched
// graceful shutdown can be done here
//
// Read more here: https://github.com/kataras/iris/blob/master/HISTORY.md#608---609
// Example: https://github.com/iris-contrib/examples/tree/master/os_interrupt
func (fn PostInterruptFunc) PostInterrupt(station *Framework) {
fn(station)
}
// 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
func (fn PreCloseFunc) PreClose(station *Framework) {
fn(station)
}
// 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
func (fn PreDownloadFunc) PreDownload(pl Plugin, downloadURL string) {
fn(pl, downloadURL)
}
//
var _ PluginDownloadManager = &pluginDownloadManager{}
var _ PluginContainer = &pluginContainer{}
// DirectoryExists returns true if a given local directory exists
func (d *pluginDownloadManager) DirectoryExists(dir string) bool {
return fs.DirectoryExists(dir)
}
// DownloadZip downlodas a zip to the given local path location
func (d *pluginDownloadManager) DownloadZip(zipURL string, targetDir string) (string, error) {
return fs.DownloadZip(zipURL, targetDir, true)
}
// Unzip unzips a zip to the given local path location
func (d *pluginDownloadManager) Unzip(archive string, target string) (string, error) {
return fs.DownloadZip(archive, target, true)
}
// Remove deletes/removes/rm a file
func (d *pluginDownloadManager) Remove(filePath string) error {
return fs.RemoveFile(filePath)
}
// Install is just the flow of the: DownloadZip->Unzip->Remove the zip
func (d *pluginDownloadManager) Install(remoteFileZip string, targetDirectory string) (string, error) {
return fs.Install(remoteFileZip, targetDirectory, true)
}
// pluginContainer is the base container of all Iris, registered plugins
type pluginContainer struct {
activatedPlugins []Plugin
customEvents map[string][]func()
downloader *pluginDownloadManager
logger *log.Logger
mu *sync.Mutex
fired map[string]int // event/plugin type name and the times fired
}
// newPluginContainer receives a logger and returns a new PluginContainer
func newPluginContainer(l *log.Logger) PluginContainer {
return &pluginContainer{logger: l, fired: make(map[string]int, 0), mu: &sync.Mutex{}}
}
// Add activates the plugins and if succeed then adds it to the activated plugins list
func (p *pluginContainer) Add(plugins ...Plugin) error {
for _, plugin := range plugins {
if p.activatedPlugins == nil {
p.activatedPlugins = make([]Plugin, 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.(pluginActivate); ok {
tempPluginContainer := *p
err := pluginObj.Activate(&tempPluginContainer)
if err != nil {
return errPluginActivate.Format(pName, err.Error())
}
tempActivatedPluginsLen := len(tempPluginContainer.activatedPlugins)
if tempActivatedPluginsLen != len(p.activatedPlugins)+tempActivatedPluginsLen+1 { // see test: plugin_test.go TestPluginActivate && TestPluginActivationError
p.activatedPlugins = tempPluginContainer.activatedPlugins
}
}
// All ok, add it to the plugins list
p.activatedPlugins = append(p.activatedPlugins, plugin)
}
return nil
}
// 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 {
if p.activatedPlugins == nil {
return errPluginRemoveNoPlugins
}
if pluginName == "" {
//return error: cannot delete an unamed plugin
return errPluginRemoveEmptyName
}
indexToRemove := -1
for i := range p.activatedPlugins {
if p.GetName(p.activatedPlugins[i]) == pluginName { // Note: if GetName is not implemented then the name is "" which is != with the plugiName, we checked this before.
indexToRemove = i
}
}
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
}
p.activatedPlugins = append(p.activatedPlugins[:indexToRemove], p.activatedPlugins[indexToRemove+1:]...)
return nil
}
// Len returns the number of activate plugins
func (p *pluginContainer) Len() int {
return len(p.activatedPlugins)
}
// GetName returns the name of a plugin, if no GetName() implemented it returns an empty string ""
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 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) Plugin {
if p.activatedPlugins == nil {
return nil
}
for i := range p.activatedPlugins {
if pluginObj, ok := p.activatedPlugins[i].(pluginGetName); ok {
if pluginObj.GetName() == pluginName {
return pluginObj
}
}
}
return nil
}
// GetAll returns all activated plugins
func (p *pluginContainer) GetAll() []Plugin {
return p.activatedPlugins
}
// GetDownloader returns the download manager
func (p *pluginContainer) GetDownloader() PluginDownloadManager {
// create it if and only if it used somewhere
if p.downloader == nil {
p.downloader = &pluginDownloadManager{}
}
return p.downloader
}
// Printf sends plain text to any registered 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{}) {
if p.logger != nil {
p.logger.Printf(format, a...) //for now just this.
}
}
// fire adds a fired event on the (statically type named) map and returns the new times
func (p *pluginContainer) fire(name string) int {
p.mu.Lock()
var times int
// maybe unnecessary but for clarity reasons
if t, found := p.fired[name]; found {
times = t
}
times++
p.fired[name] = times
p.mu.Unlock()
return times
}
// Fired receives an event name/plugin type and returns the times which this event is fired and how many plugins are fired this event,
// if zero then it's not fired at all
func (p *pluginContainer) Fired(name string) (times int) {
if t, found := p.fired[name]; found {
times = t
}
return
}
// PreLookup adds a PreLookup plugin-function to the plugin flow container
func (p *pluginContainer) PreLookup(fn PreLookupFunc) {
p.Add(fn)
}
// DoPreLookup raise all plugins which has the PreLookup method
func (p *pluginContainer) DoPreLookup(r Route) {
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].(pluginPreLookup); ok {
// fire will add times to the number of events fired this event
p.fire("prelookup")
pluginObj.PreLookup(r)
}
}
}
// PreLookupFired returns true if PreLookup event/ plugin type is fired at least one time
func (p *pluginContainer) PreLookupFired() bool {
return p.Fired("prelookup") > 0
}
// PreBuild adds a PreBuild plugin-function to the plugin flow container
func (p *pluginContainer) PreBuild(fn PreBuildFunc) {
p.Add(fn)
}
// DoPreBuild raise all plugins that have the PreBuild method
func (p *pluginContainer) DoPreBuild(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].(pluginPreBuild); ok {
pluginObj.PreBuild(station)
p.fire("prebuild")
}
}
}
// PreBuildFired returns true if PreBuild event/ plugin type is fired at least one time
func (p *pluginContainer) PreBuildFired() bool {
return p.Fired("prebuild") > 0
}
// PreListen adds a PreListen plugin-function to the plugin flow container
func (p *pluginContainer) PreListen(fn PreListenFunc) {
p.Add(fn)
}
// DoPreListen raise all plugins which has the PreListen method
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].(pluginPreListen); ok {
pluginObj.PreListen(station)
p.fire("prelisten")
}
}
}
// 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)
p.fire("prelisten")
}
wg.Done()
}(plugin)
}
wg.Wait()
}
// PreListenFired returns true if PreListen or PreListenParallel event/ plugin type is fired at least one time
func (p *pluginContainer) PreListenFired() bool {
return p.Fired("prelisten") > 0
}
// PostListen adds a PostListen plugin-function to the plugin flow container
func (p *pluginContainer) PostListen(fn PostListenFunc) {
p.Add(fn)
}
// DoPostListen raise all plugins which has the DoPostListen method
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].(pluginPostListen); ok {
pluginObj.PostListen(station)
p.fire("postlisten")
}
}
}
// PostListenFired returns true if PostListen event/ plugin type is fired at least one time
func (p *pluginContainer) PostListenFired() bool {
return p.Fired("postlisten") > 0
}
// PostInterrupt adds a PostInterrupt plugin-function to the plugin flow container
//
// Read more here: https://github.com/kataras/iris/blob/master/HISTORY.md#608---609
// Example: https://github.com/iris-contrib/examples/tree/master/os_interrupt
func (p *pluginContainer) PostInterrupt(fn PostInterruptFunc) {
p.Add(fn)
}
// DoPostInterrupt raise all plugins which has the PostInterrupt method
func (p *pluginContainer) DoPostInterrupt(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].(pluginPostInterrupt); ok {
pluginObj.PostInterrupt(station)
p.fire("postinterrupt")
}
}
}
// PostInterruptFired returns true if PostInterrupt event/ plugin type is fired at least one time
func (p *pluginContainer) PostInterruptFired() bool {
return p.Fired("postinterrupt") > 0
}
// PreClose adds a PreClose plugin-function to the plugin flow container
func (p *pluginContainer) PreClose(fn PreCloseFunc) {
p.Add(fn)
}
// DoPreClose raise all plugins which has the DoPreClose method
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].(pluginPreClose); ok {
pluginObj.PreClose(station)
p.fire("preclose")
}
}
}
// PreCloseFired returns true if PreCLose event/ plugin type is fired at least one time
func (p *pluginContainer) PreCloseFired() bool {
return p.Fired("preclose") > 0
}
// PreDownload adds a PreDownload plugin-function to the plugin flow container
func (p *pluginContainer) PreDownload(fn PreDownloadFunc) {
p.Add(fn)
}
// DoPreDownload raise all plugins which has the DoPreDownload method
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].(pluginPreDownload); ok {
pluginObj.PreDownload(pluginTryToDownload, downloadURL)
p.fire("predownload")
}
}
}
// PreDownloadFired returns true if PreDownload event/ plugin type is fired at least one time
func (p *pluginContainer) PreDownloadFired() bool {
return p.Fired("predownload") > 0
}

View File

@ -1,205 +0,0 @@
// Black-box Testing
package iris_test
import (
"fmt"
"github.com/kataras/iris"
"testing"
)
const (
testPluginExDescription = "Description for My test plugin"
testPluginExName = "My test plugin"
)
type testPluginEx struct {
named, activated, descriptioned bool
prelistenran, postlistenran, precloseran bool
}
func (t *testPluginEx) GetName() string {
fmt.Println("GetName Struct")
t.named = true
return testPluginExName
}
func (t *testPluginEx) GetDescription() string {
fmt.Println("GetDescription Struct")
t.descriptioned = true
return testPluginExDescription
}
func (t *testPluginEx) Activate(p iris.PluginContainer) error {
fmt.Println("Activate Struct")
t.activated = true
return nil
}
func (t *testPluginEx) PreListen(*iris.Framework) {
fmt.Println("PreListen Struct")
t.prelistenran = true
}
func (t *testPluginEx) PostListen(*iris.Framework) {
fmt.Println("PostListen Struct")
t.postlistenran = true
}
func (t *testPluginEx) PreClose(*iris.Framework) {
fmt.Println("PreClose Struct")
t.precloseran = true
}
func ExamplePlugins_Add() {
iris.ResetDefault()
iris.Default.Set(iris.OptionDisableBanner(true))
iris.Plugins.Add(iris.PreListenFunc(func(*iris.Framework) {
fmt.Println("PreListen Func")
}))
iris.Plugins.Add(iris.PostListenFunc(func(*iris.Framework) {
fmt.Println("PostListen Func")
}))
iris.Plugins.Add(iris.PreCloseFunc(func(*iris.Framework) {
fmt.Println("PreClose Func")
}))
myplugin := &testPluginEx{}
iris.Plugins.Add(myplugin)
desc := iris.Plugins.GetDescription(myplugin)
fmt.Println(desc)
// travis have problems if I do that using
// Listen(":8080") and Close()
iris.Plugins.DoPreListen(iris.Default)
iris.Plugins.DoPostListen(iris.Default)
iris.Plugins.DoPreClose(iris.Default)
// Output:
// GetName Struct
// Activate Struct
// GetDescription Struct
// Description for My test plugin
// PreListen Func
// PreListen Struct
// PostListen Func
// PostListen Struct
// PreClose Func
// PreClose Struct
}
// if a plugin has GetName, then it should be registered only one time, the name exists for that reason, it's like unique ID
func TestPluginDublicateName(t *testing.T) {
iris.ResetDefault()
var plugins = iris.Default.Plugins
firstNamedPlugin := &testPluginEx{}
sameNamedPlugin := &testPluginEx{}
// err := plugins.Add(firstNamedPlugin, sameNamedPlugin) or
err := plugins.Add(firstNamedPlugin)
if err != nil {
t.Fatalf("Unexpected error when adding a plugin with name: %s", testPluginExName)
}
err = plugins.Add(sameNamedPlugin)
if err == nil {
t.Fatalf("Expected an error because of dublicate named plugin!")
}
if plugins.Len() != 1 {
t.Fatalf("Expected: %d activated plugin but we got: %d", 1, plugins.Len())
}
}
type testPluginActivationType struct {
shouldError bool
}
func (t testPluginActivationType) Activate(p iris.PluginContainer) error {
p.Add(&testPluginEx{})
if t.shouldError {
return fmt.Errorf("An error happens, this plugin and the added plugins by this plugin should not be registered")
}
return nil
}
// a plugin may contain children plugins too, but,
// if an error happened then all of them are not activated/added to the plugin container
func AddPluginTo(t *testing.T, plugins iris.PluginContainer, plugin iris.Plugin, expectingCount int) {
plugins.Add(plugin)
if plugins.Len() != expectingCount { // 2 because it registers a second plugin also
t.Fatalf("Expected activated plugins to be: %d but we got: %d", expectingCount, plugins.Len())
}
}
// if any error returned from the Activate plugin's method,
// then this plugin and the plugins it registers should not be registered at all
func TestPluginActivate(t *testing.T) {
iris.ResetDefault()
plugins := iris.Plugins
myValidPluginWithChild := testPluginActivationType{shouldError: false}
AddPluginTo(t, plugins, myValidPluginWithChild, 2) // 2 because its children registered also (its parent is not throwing an error)
myInvalidPlugin := testPluginActivationType{shouldError: true}
// should stay 2, even if we tried to add a new one,
// because it cancels the registration of its children too (shouldError = true)
AddPluginTo(t, plugins, myInvalidPlugin, 2)
}
func TestPluginEvents(t *testing.T) {
iris.ResetDefault()
var plugins = iris.Default.Plugins
var prelistenran, postlistenran, precloseran bool
plugins.Add(iris.PreListenFunc(func(*iris.Framework) {
prelistenran = true
}))
plugins.Add(iris.PostListenFunc(func(*iris.Framework) {
postlistenran = true
}))
plugins.Add(iris.PreCloseFunc(func(*iris.Framework) {
precloseran = true
}))
myplugin := &testPluginEx{}
plugins.Add(myplugin)
if plugins.Len() != 4 {
t.Fatalf("Expected: %d plugins to be registered but we got: %d", 4, plugins.Len())
}
desc := plugins.GetDescription(myplugin)
if desc != testPluginExDescription {
t.Fatalf("Expected: %s as Description of the plugin but got: %s", testPluginExDescription, desc)
}
plugins.DoPreListen(nil)
plugins.DoPostListen(nil)
plugins.DoPreClose(nil)
if !prelistenran {
t.Fatalf("Expected to run PreListen Func but it doesn't!")
}
if !postlistenran {
t.Fatalf("Expected to run PostListen Func but it doesn't!")
}
if !precloseran {
t.Fatalf("Expected to run PostListen Func but it doesn't!")
}
if !myplugin.named {
t.Fatalf("Plugin should be named with: %s!", testPluginExName)
}
if !myplugin.activated {
t.Fatalf("Plugin should be activated but it's not!")
}
if !myplugin.prelistenran {
t.Fatalf("Expected to run PreListen Struct but it doesn't!")
}
if !myplugin.postlistenran {
t.Fatalf("Expected to run PostListen Struct but it doesn't!")
}
if !myplugin.precloseran {
t.Fatalf("Expected to run PostListen Struct but it doesn't!")
}
}

View File

@ -1,19 +0,0 @@
# Plugins
Navigate through [iris-contrib/plugin](https://github.com/iris-contrib/plugin) repository to view all available 'plugins'.
> By the word 'plugin', we mean an event-driven system and not the future go1.8 plugin feature.
## Installation
```sh
$ go get github.com/iris-contrib/plugin/...
```
## How can I register a plugin?
```go
app := iris.New()
app.Plugins.Add(thePlugin)
```

432
policy.go Normal file
View File

@ -0,0 +1,432 @@
package iris
import (
"io"
"log"
"net/http"
"strings"
"github.com/kataras/go-errors"
)
type (
// Policy is an interface which should be implemented by all
// modules that can adapt a policy to the Framework.
// With a Policy you can change the behavior of almost each of the existing Iris' features.
Policy interface {
// Adapt receives the main *Policies which the Policy should be attached on.
Adapt(frame *Policies)
}
// Policies is the main policies list, the rest of the objects that implement the Policy
// are adapted to the object which contains a field of type *Policies.
//
// Policies can have nested policies behaviors too.
// See iris.go field: 'policies' and function 'Adapt' for more.
Policies struct {
LoggerPolicy
EventPolicy
RouterReversionPolicy
RouterBuilderPolicy
RouterWrapperPolicy
RenderPolicy
TemplateFuncsPolicy
}
)
// Adapt implements the behavior in order to be valid to pass Policies as one
// useful for third-party libraries which can provide more tools in one registration.
func (p Policies) Adapt(frame *Policies) {
// Adapt the logger (optionally, it defaults to a log.New(...).Printf)
if p.LoggerPolicy != nil {
p.LoggerPolicy.Adapt(frame)
}
// Adapt the flow callbacks (optionally)
p.EventPolicy.Adapt(frame)
// Adapt the reverse routing behaviors and policy
p.RouterReversionPolicy.Adapt(frame)
// Adapt the router builder
if p.RouterBuilderPolicy != nil {
p.RouterBuilderPolicy.Adapt(frame)
}
// Adapt any Router's wrapper (optionally)
if p.RouterWrapperPolicy != nil {
p.RouterWrapperPolicy.Adapt(frame)
}
// Adapt the render policy (both templates and rich content)
if p.RenderPolicy != nil {
p.RenderPolicy.Adapt(frame)
}
// Adapt the template funcs which can be used to register template funcs
// from community's packages, it doesn't matters what template/view engine the user
// uses, and if uses at all.
if p.TemplateFuncsPolicy != nil {
p.TemplateFuncsPolicy.Adapt(frame)
}
}
// LogMode is the type for the LoggerPolicy write mode.
// Two modes available:
// - ProdMode (production level mode)
// - DevMode (development level mode)
//
// The ProdMode should output only fatal errors
// The DevMode ouputs the rest of the errors
//
// Iris logs ONLY errors at both cases.
// By-default ONLY ProdMode level messages are printed to the os.Stdout.
type LogMode uint8
const (
// ProdMode the production level logger write mode,
// responsible to fatal errors, errors that happen which
// your app can't continue running.
ProdMode LogMode = iota
// DevMode is the development level logger write mode,
// responsible to the rest of the errors, for example
// if you set a app.Favicon("myfav.ico"..) and that fav doesn't exists
// in your system, then it printed by DevMode and app.Favicon simple doesn't works.
// But the rest of the app can continue running, so it's not 'Fatal error'
DevMode
)
// LoggerPolicy is a simple interface which is used to log mostly system panics
// exception for general debugging messages is when the `Framework.Config.IsDevelopment = true`.
// It should prints to the logger.
// Arguments should be handled in the manner of fmt.Printf.
type LoggerPolicy func(mode LogMode, log string)
// Adapt addapts a Logger to the main policies.
func (l LoggerPolicy) Adapt(frame *Policies) {
if l != nil {
// notes for me: comment these in order to remember
// why I choose not to do that:
// It wraps the loggers, so you can use more than one
// when you have multiple print targets.
// No this is not a good idea for loggers
// the user may not expecting this behavior,
// if the user wants multiple targets she/he
// can wrap their loggers or use one logger to print on all targets.
// COMMENT:
// logger := l
// if frame.LoggerPolicy != nil {
// prevLogger := frame.LoggerPolicy
// nextLogger := l
// logger = func(mode LogMode, log string) {
// prevLogger(mode, log)
// nextLogger(mode, log)
// }
// }
frame.LoggerPolicy = l
}
}
// The write method exists to LoggerPolicy to be able to passed
// as a valid an io.Writer when you need it.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
//
// Note: this Write writes as the Production Env, so the default logger should be able to log this messages
// coming from internal http.Server (mostly)
// you can change this behavior too.
func (l LoggerPolicy) Write(p []byte) (n int, err error) {
log := string(p)
l(ProdMode, log)
return len(log), nil
}
// ToLogger returns a new *log.Logger
// which prints to the the LoggerPolicy function
// this is used when your packages needs explicit an *log.Logger.
//
// Note: Each time you call it, it returns a new *log.Logger.
func (l LoggerPolicy) ToLogger(flag int) *log.Logger {
return log.New(l, "", flag)
}
type (
// EventListener is the signature for type of func(*Framework),
// which is used to register events inside an EventPolicy.
//
// Keep note that, inside the policy this is a wrapper
// in order to register more than one listener without the need of slice.
EventListener func(*Framework)
// EventPolicy contains the available Framework's flow event callbacks.
// Available events:
// - Boot
// - Build
// - Interrupted
// - Recover
EventPolicy struct {
// Boot with a listener type of EventListener.
// Fires when '.Boot' is called (by .Serve functions or manually),
// before the Build of the components and the Listen,
// after VHost and VSCheme configuration has been setted.
Boot EventListener
// Before Listen, after Boot
Build EventListener
// Interrupted with a listener type of EventListener.
// Fires after the terminal is interrupted manually by Ctrl/Cmd + C
// which should be used to release external resources.
// Iris will close and os.Exit at the end of custom interrupted events.
// If you want to prevent the default behavior just block on the custom Interrupted event.
Interrupted EventListener
// Recover with a listener type of func(*Framework, interface{}).
// Fires when an unexpected error(panic) is happening at runtime,
// while the server's net.Listener accepting requests
// or when a '.Must' call contains a filled error.
// Used to release external resources and '.Close' the server.
// Only one type of this callback is allowed.
//
// If not empty then the Framework will skip its internal
// server's '.Close' and panic to its '.Logger' and execute that callback instaed.
// Differences from Interrupted:
// 1. Fires on unexpected errors
// 2. Only one listener is allowed.
Recover func(*Framework, error)
}
)
var _ Policy = EventPolicy{}
// Adapt adaps an EventPolicy object to the main *Policies.
func (e EventPolicy) Adapt(frame *Policies) {
// Boot event listener, before the build (old: PreBuild)
frame.EventPolicy.Boot =
wrapEvtListeners(frame.EventPolicy.Boot, e.Boot)
// Build event listener, after Boot and before Listen(old: PostBuild & PreListen)
frame.EventPolicy.Build =
wrapEvtListeners(frame.EventPolicy.Build, e.Build)
// Interrupted event listener, when control+C or manually interrupt by os signal
frame.EventPolicy.Interrupted =
wrapEvtListeners(frame.EventPolicy.Interrupted, e.Interrupted)
// Recover event listener, when panic on .Must and inside .Listen/ListenTLS/ListenUNIX/ListenLETSENCRYPT/Serve
// only one allowed, no wrapper is used.
if e.Recover != nil {
frame.EventPolicy.Recover = e.Recover
}
}
// Fire fires an EventListener with its Framework when listener is not nil.
// Returns true when fired, otherwise false.
func (e EventPolicy) Fire(ln EventListener, s *Framework) bool {
if ln != nil {
ln(s)
return true
}
return false
}
func wrapEvtListeners(prev EventListener, next EventListener) EventListener {
if next == nil {
return prev
}
listener := next
if prev != nil {
listener = func(s *Framework) {
prev(s)
next(s)
}
}
return listener
}
type (
// RouterReversionPolicy is used for the reverse routing feature on
// which custom routers should create and adapt to the Policies.
RouterReversionPolicy struct {
// StaticPath should return the static part of the route path
// for example, with the default router (: and *):
// /api/user/:userid should return /api/user
// /api/user/:userid/messages/:messageid should return /api/user
// /dynamicpath/*path should return /dynamicpath
// /my/path should return /my/path
StaticPath func(path string) string
// WildcardPath should return a path converted to a 'dynamic' path
// for example, with the default router(wildcard symbol: '*'):
// ("/static", "path") should return /static/*path
// ("/myfiles/assets", "anything") should return /myfiles/assets/*anything
WildcardPath func(path string, paramName string) string
// URLPath used for reverse routing on templates with {{ url }} and {{ path }} funcs.
// Receives the route name and arguments and returns its http path
URLPath func(r RouteInfo, args ...string) string
// RouteContextLinker should put the route's handlers and named parameters(if any) to the ctx
// it's used to execute virtually an "offline" route
// against a context like it was requested by user, but it is not.
RouteContextLinker func(r RouteInfo, ctx *Context)
}
// RouterBuilderPolicy is the most useful Policy for custom routers.
// A custom router should adapt this policy which is a func
// accepting a route repository (contains all necessary routes information)
// and a context pool which should be used inside router's handlers.
RouterBuilderPolicy func(repo RouteRepository, cPool ContextPool) http.Handler
// RouterWrapperPolicy is the Policy which enables a wrapper on the top of
// the builded Router. Usually it's useful for third-party middleware
// when need to wrap the entire application with a middleware like CORS.
RouterWrapperPolicy func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)
)
func normalizePath(path string) string {
path = strings.Replace(path, "//", "/", -1)
if len(path) > 1 && strings.IndexByte(path, '/') == len(path)-1 {
// if it's not "/" and ending with slash remove that slash
path = path[0 : len(path)-2]
}
return path
}
// Adapt adaps a RouterReversionPolicy object to the main *Policies.
func (r RouterReversionPolicy) Adapt(frame *Policies) {
if r.StaticPath != nil {
staticPathFn := r.StaticPath
frame.RouterReversionPolicy.StaticPath = func(path string) string {
return staticPathFn(normalizePath(path))
}
}
if r.WildcardPath != nil {
wildcardPathFn := r.WildcardPath
frame.RouterReversionPolicy.WildcardPath = func(path string, paramName string) string {
return wildcardPathFn(normalizePath(path), paramName)
}
}
if r.URLPath != nil {
frame.RouterReversionPolicy.URLPath = r.URLPath
}
if r.RouteContextLinker != nil {
frame.RouterReversionPolicy.RouteContextLinker = r.RouteContextLinker
}
}
// Adapt adaps a RouterBuilderPolicy object to the main *Policies.
func (r RouterBuilderPolicy) Adapt(frame *Policies) {
// What is this kataras?
// The whole design of this file is brilliant = go's power + my ideas and experience on software architecture.
//
// When the router decides to compile/build this behavior
// then this overload will check for a wrapper too
// if a wrapper exists it will wrap the result of the RouterBuilder (which is http.Handler, the Router.)
// and return that instead.
// I moved the logic here so we don't need a 'compile/build' method inside the routerAdaptor.
frame.RouterBuilderPolicy = RouterBuilderPolicy(func(repo RouteRepository, cPool ContextPool) http.Handler {
handler := r(repo, cPool)
wrapper := frame.RouterWrapperPolicy
if wrapper != nil {
originalHandler := handler.ServeHTTP
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
wrapper(w, r, originalHandler)
})
}
return handler
})
}
// Adapt adaps a RouterWrapperPolicy object to the main *Policies.
func (r RouterWrapperPolicy) Adapt(frame *Policies) {
frame.RouterWrapperPolicy = r
}
// RenderPolicy is the type which you can adapt custom renderers
// based on the 'name', simple as that.
// Note that the whole template view system and
// content negotiation works by setting this function via other adaptors.
//
// The functions are wrapped, like any other policy func, the only difference is that
// here the developer has a priority over the defaults:
// - the last registered is trying to be executed first
// - the first registered is executing last.
// So a custom adaptor that the community can create and share with each other
// can override the existing one with just a simple registration.
type RenderPolicy func(out io.Writer, name string, bind interface{}, options ...map[string]interface{}) (error, bool)
// Adapt adaps a RenderPolicy object to the main *Policies.
func (r RenderPolicy) Adapt(frame *Policies) {
if r != nil {
renderer := r
prevRenderer := frame.RenderPolicy
if prevRenderer != nil {
nextRenderer := r
renderer = func(out io.Writer, name string, binding interface{}, options ...map[string]interface{}) (error, bool) {
// Remember: RenderPolicy works in the opossite order of declaration,
// the last registered is trying to be executed first,
// the first registered is executing last.
err, ok := nextRenderer(out, name, binding, options...)
if !ok {
prevErr, prevOk := prevRenderer(out, name, binding, options...)
if err != nil {
if prevErr != nil {
err = errors.New(prevErr.Error()).Append(err.Error())
}
}
if prevOk {
ok = true
}
}
// this renderer is responsible for this name
// but it has an error, so don't continue to the next
return err, ok
}
}
frame.RenderPolicy = renderer
}
}
// TemplateFuncsPolicy sets or overrides template func map.
// Defaults are the iris.URL and iris.Path, all the template engines supports the following:
// {{ url "mynamedroute" "pathParameter_ifneeded"} }
// {{ urlpath "mynamedroute" "pathParameter_ifneeded" }}
// {{ render "header.html" }}
// {{ render_r "header.html" }} // partial relative path to current page
// {{ yield }}
// {{ current }}
//
// Developers can already set the template's func map from the view adaptors, example: view.HTML(...).Funcs(...)),
// this type exists in order to be possible from third-party developers to create packages that bind template functions
// to the Iris without the need of knowing what template engine is used by the user or
// what order of declaration the user should follow.
type TemplateFuncsPolicy map[string]interface{} // interface can be: func(arguments ...string) string {}
// Adapt adaps a TemplateFuncsPolicy object to the main *Policies.
func (t TemplateFuncsPolicy) Adapt(frame *Policies) {
if len(t) > 0 {
if frame.TemplateFuncsPolicy == nil {
frame.TemplateFuncsPolicy = t
return
}
if frame.TemplateFuncsPolicy != nil {
for k, v := range t {
// set or replace the existing
frame.TemplateFuncsPolicy[k] = v
}
}
}
}

View File

@ -148,14 +148,13 @@ func (w *ResponseRecorder) flushResponse() {
w.responseWriter.flushResponse()
if len(w.chunks) > 0 {
// ignore error
w.responseWriter.Write(w.chunks)
}
}
// Flush sends any buffered data to the client.
func (w *ResponseRecorder) Flush() {
w.flushResponse()
w.responseWriter.Flush()
w.ResetBody()
}
@ -250,6 +249,7 @@ func (w *ResponseRecorder) writeTo(res ResponseWriter) {
// append the body
if len(w.chunks) > 0 {
// ignore error
to.Write(w.chunks)
}

View File

@ -202,7 +202,7 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return h.Hijack()
}
return nil, nil, errors.New("Hijack is not supported by this ResponseWriter.")
return nil, nil, errors.New("hijack is not supported by this ResponseWriter")
}
// Flush sends any buffered data to the client.

View File

@ -1,158 +0,0 @@
package iris_test
import (
"fmt"
"testing"
"github.com/kataras/iris"
"github.com/kataras/iris/httptest"
)
// most tests lives inside context_test.go:Transactions, there lives the response writer's full and coblex tests
func TestResponseWriterBeforeFlush(t *testing.T) {
api := iris.New()
body := "my body"
beforeFlushBody := "body appeneded or setted before callback"
api.Get("/", func(ctx *iris.Context) {
w := ctx.ResponseWriter
w.SetBeforeFlush(func() {
w.WriteString(beforeFlushBody)
})
w.WriteString(body)
})
// recorder can change the status code after write too
// it can also be changed everywhere inside the context's lifetime
api.Get("/recorder", func(ctx *iris.Context) {
w := ctx.Recorder()
w.SetBeforeFlush(func() {
w.SetBodyString(beforeFlushBody)
w.WriteHeader(iris.StatusForbidden)
})
w.WriteHeader(iris.StatusOK)
w.WriteString(body)
})
e := httptest.New(api, t)
e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(body + beforeFlushBody)
e.GET("/recorder").Expect().Status(iris.StatusForbidden).Body().Equal(beforeFlushBody)
}
func TestResponseWriterToRecorderMiddleware(t *testing.T) {
api := iris.New()
beforeFlushBody := "body appeneded or setted before callback"
api.UseGlobal(iris.Recorder)
api.Get("/", func(ctx *iris.Context) {
w := ctx.Recorder()
w.SetBeforeFlush(func() {
w.SetBodyString(beforeFlushBody)
w.WriteHeader(iris.StatusForbidden)
})
w.WriteHeader(iris.StatusOK)
w.WriteString("this will not be sent at all because of SetBodyString")
})
e := httptest.New(api, t)
e.GET("/").Expect().Status(iris.StatusForbidden).Body().Equal(beforeFlushBody)
}
func TestResponseRecorderStatusCodeContentTypeBody(t *testing.T) {
api := iris.New()
firstStatusCode := iris.StatusOK
contentType := "text/html; charset=" + api.Config.Charset
firstBodyPart := "first"
secondBodyPart := "second"
prependedBody := "zero"
expectedBody := prependedBody + firstBodyPart + secondBodyPart
api.Use(iris.Recorder)
// recorder's status code can change if needed by a middleware or the last handler.
api.UseFunc(func(ctx *iris.Context) {
ctx.SetStatusCode(firstStatusCode)
ctx.Next()
})
api.UseFunc(func(ctx *iris.Context) {
ctx.SetContentType(contentType)
ctx.Next()
})
api.UseFunc(func(ctx *iris.Context) {
// set a body ( we will append it later, only with response recorder we can set append or remove a body or a part of it*)
ctx.WriteString(firstBodyPart)
ctx.Next()
})
api.UseFunc(func(ctx *iris.Context) {
ctx.WriteString(secondBodyPart)
ctx.Next()
})
api.Get("/", func(ctx *iris.Context) {
previousStatusCode := ctx.StatusCode()
if previousStatusCode != firstStatusCode {
t.Fatalf("Previous status code should be %d but got %d", firstStatusCode, previousStatusCode)
}
previousContentType := ctx.ContentType()
if previousContentType != contentType {
t.Fatalf("First content type should be %s but got %d", contentType, previousContentType)
}
// change the status code, this will tested later on (httptest)
ctx.SetStatusCode(iris.StatusForbidden)
prevBody := string(ctx.Recorder().Body())
if prevBody != firstBodyPart+secondBodyPart {
t.Fatalf("Previous body (first handler + second handler's writes) expected to be: %s but got: %s", firstBodyPart+secondBodyPart, prevBody)
}
// test it on httptest later on
ctx.Recorder().SetBodyString(prependedBody + prevBody)
})
e := httptest.New(api, t)
et := e.GET("/").Expect().Status(iris.StatusForbidden)
et.Header("Content-Type").Equal(contentType)
et.Body().Equal(expectedBody)
}
func ExampleResponseWriter_WriteHeader() {
// func TestResponseWriterMultipleWriteHeader(t *testing.T) {
iris.ResetDefault()
iris.Default.Set(iris.OptionDisableBanner(true))
expectedOutput := "Hey"
iris.Get("/", func(ctx *iris.Context) {
// here
for i := 0; i < 10; i++ {
ctx.ResponseWriter.WriteHeader(iris.StatusOK)
}
ctx.Writef(expectedOutput)
// here
fmt.Println(expectedOutput)
// here
for i := 0; i < 10; i++ {
ctx.SetStatusCode(iris.StatusOK)
}
})
e := httptest.New(iris.Default, nil)
e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedOutput)
// here it shouldn't log an error that status code write multiple times (by the net/http package.)
// Output:
// Hey
}

332
route.go Normal file
View File

@ -0,0 +1,332 @@
package iris
import (
"sort"
"strings"
)
type (
// RouteInfo is just the (other idea was : RouteInfo but we needed the Change/SetName visible so...)
// information of the registered routes.
RouteInfo interface {
// ChangeName & AllowOPTIONS are the only one route property
// which can be change without any bad side-affects
// so it's the only setter here.
//
// It's used on iris.Default.Handle()
ChangeName(name string) RouteInfo
// Name returns the name of the route
Name() string
// Method returns the http method
Method() string
// AllowOPTIONS called when this route is targeting OPTIONS methods too
// it's an alternative way of registring the same route with '.OPTIONS("/routepath", routeMiddleware)'
AllowOPTIONS() RouteInfo
// HasCors returns true if the route is targeting OPTIONS methods too
// or it has a middleware which conflicts with "httpmethod",
// otherwise false
HasCors() bool
// Subdomain returns the subdomain,if any
Subdomain() string
// Path returns the path
Path() string
// Middleware returns the slice of Handler([]Handler) registered to this route
Middleware() Middleware
// IsOnline returns true if the route is marked as "online" (state)
IsOnline() bool
}
// route holds useful information about route
route struct {
// if no name given then it's the subdomain+path
name string
subdomain string
method string
allowOptionsMethod bool
path string
middleware Middleware
}
)
var _ RouteInfo = &route{}
// RouteConflicts checks for route's middleware conflicts
func RouteConflicts(r RouteInfo, 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
}
// Name returns the name of the route
func (r route) Name() string {
return r.name
}
// Name returns the name of the route
func (r *route) ChangeName(name string) RouteInfo {
r.name = name
return r
}
// AllowOPTIONS called when this route is targeting OPTIONS methods too
// it's an alternative way of registring the same route with '.OPTIONS("/routepath", routeMiddleware)'
func (r *route) AllowOPTIONS() RouteInfo {
r.allowOptionsMethod = true
return r
}
// Method returns the http method
func (r route) Method() string {
return r.method
}
// Subdomain returns the subdomain,if any
func (r route) Subdomain() string {
return r.subdomain
}
// Path returns the path
func (r route) Path() string {
return r.path
}
// Middleware returns the slice of Handler([]Handler) registered to this route
func (r route) Middleware() Middleware {
return r.middleware
}
// IsOnline returns true if the route is marked as "online" (state)
func (r route) IsOnline() bool {
return r.method != MethodNone
}
// HasCors returns true if the route is targeting OPTIONS methods too
// or it has a middleware which conflicts with "httpmethod",
// otherwise false
func (r *route) HasCors() bool {
return r.allowOptionsMethod || RouteConflicts(r, "httpmethod")
}
// MethodChangedListener listener signature fired when route method changes
type MethodChangedListener func(routeInfo RouteInfo, oldMethod string)
// RouteRepository contains the interface which is used on custom routers
// contains methods and helpers to find a route by its name,
// and change its method, path, middleware.
//
// This is not visible outside except the RouterBuilderPolicy
type RouteRepository interface { // RouteEngine kai ContextEngine mesa sto builder adi gia RouteRepository kai ContextEngine
RoutesInfo
ChangeName(routeInfo RouteInfo, newName string)
ChangeMethod(routeInfo RouteInfo, newMethod string)
ChangePath(routeInfo RouteInfo, newPath string)
ChangeMiddleware(routeInfo RouteInfo, newMiddleware Middleware)
}
// RoutesInfo is the interface which contains the valid actions
// permitted at RUNTIME
type RoutesInfo interface { // RouteRepository
Lookup(routeName string) RouteInfo
Visit(visitor func(RouteInfo))
OnMethodChanged(methodChangedListener MethodChangedListener)
Online(routeInfo RouteInfo, HTTPMethod string) bool
Offline(routeInfo RouteInfo) bool
}
// routeRepository contains all the routes.
// Implements both RouteRepository and RoutesInfo
type routeRepository struct {
routes []*route
// when builded (TODO: move to its own struct)
methodChangedListeners []MethodChangedListener
}
var _ sort.Interface = &routeRepository{}
var _ RouteRepository = &routeRepository{}
// Len is the number of elements in the collection.
func (r routeRepository) Len() int {
return len(r.routes)
}
// Less reports whether the element with
// index i should sort before the element with index j.
func (r routeRepository) Less(i, j int) bool {
return len(r.routes[i].Subdomain()) > len(r.routes[j].Subdomain())
}
// Swap swaps the elements with indexes i and j.
func (r routeRepository) Swap(i, j int) {
r.routes[i], r.routes[j] = r.routes[j], r.routes[i]
}
func (r *routeRepository) register(method, subdomain, path string,
middleware Middleware) *route {
_route := &route{
name: method + subdomain + path,
method: method,
subdomain: subdomain,
path: path,
middleware: middleware,
}
r.routes = append(r.routes, _route)
return _route
}
func (r *routeRepository) getRouteByName(routeName string) *route {
for i := range r.routes {
_route := r.routes[i]
if _route.name == routeName {
return _route
}
}
return nil
}
// Lookup returns a route by its name
// used for reverse routing and templates
func (r *routeRepository) Lookup(routeName string) RouteInfo {
route := r.getRouteByName(routeName)
if route == nil {
return nil
}
return route
}
// ChangeName changes the Name of an existing route
func (r *routeRepository) ChangeName(routeInfo RouteInfo,
newName string) {
if newName != "" {
route := r.getRouteByName(routeInfo.Name())
if route != nil {
route.name = newName
}
}
}
func (r *routeRepository) OnMethodChanged(methodChangedListener MethodChangedListener) {
r.methodChangedListeners = append(r.methodChangedListeners, methodChangedListener)
}
func (r *routeRepository) fireMethodChangedListeners(routeInfo RouteInfo, oldMethod string) {
for i := 0; i < len(r.methodChangedListeners); i++ {
r.methodChangedListeners[i](routeInfo, oldMethod)
}
}
// ChangeMethod changes the Method of an existing route
func (r *routeRepository) ChangeMethod(routeInfo RouteInfo,
newMethod string) {
newMethod = strings.ToUpper(newMethod)
valid := false
for _, m := range AllMethods {
if newMethod == m || newMethod == MethodNone {
valid = true
}
}
if valid {
route := r.getRouteByName(routeInfo.Name())
if route != nil && route.method != newMethod {
oldMethod := route.method
route.method = newMethod
r.fireMethodChangedListeners(routeInfo, oldMethod)
}
}
}
// Online sets the state of the route to "online" with a specific http method
// it re-builds the router
//
// returns true if state was actually changed
//
// see context.ExecRoute(routeInfo),
// iris.Default.None(...) and iris.Routes.Online/.Routes.Offline
// For more details look: https://github.com/kataras/iris/issues/585
//
// Example: https://github.com/iris-contrib/examples/tree/master/route_state
func (r *routeRepository) Online(routeInfo RouteInfo, HTTPMethod string) bool {
return r.changeRouteState(routeInfo, HTTPMethod)
}
// Offline sets the state of the route to "offline" and re-builds the router
//
// returns true if state was actually changed
//
// see context.ExecRoute(routeInfo),
// iris.Default.None(...) and iris.Routes.Online/.Routes.Offline
// For more details look: https://github.com/kataras/iris/issues/585
//
// Example: https://github.com/iris-contrib/examples/tree/master/route_state
func (r *routeRepository) Offline(routeInfo RouteInfo) bool {
return r.changeRouteState(routeInfo, MethodNone)
}
// changeRouteState changes the state of the route.
// iris.MethodNone for offline
// and iris.MethodGet/MethodPost/MethodPut/MethodDelete /MethodConnect/MethodOptions/MethodHead/MethodTrace/MethodPatch for online
// it re-builds the router
//
// returns true if state was actually changed
func (r *routeRepository) changeRouteState(routeInfo RouteInfo, HTTPMethod string) bool {
if routeInfo != nil {
nonSpecificMethod := len(HTTPMethod) == 0
if routeInfo.Method() != HTTPMethod {
if nonSpecificMethod {
r.ChangeMethod(routeInfo, MethodGet) // if no method given, then do it for "GET" only
} else {
r.ChangeMethod(routeInfo, HTTPMethod)
}
// re-build the router/main handler should be implemented
// on the custom router via OnMethodChanged event.
return true
}
}
return false
}
// ChangePath changes the Path of an existing route
func (r *routeRepository) ChangePath(routeInfo RouteInfo,
newPath string) {
if newPath != "" {
route := r.getRouteByName(routeInfo.Name())
if route != nil {
route.path = newPath
}
}
}
// ChangeMiddleware changes the Middleware/Handlers of an existing route
func (r *routeRepository) ChangeMiddleware(routeInfo RouteInfo,
newMiddleware Middleware) {
route := r.getRouteByName(routeInfo.Name())
if route != nil {
route.middleware = newMiddleware
}
}
// Visit accepts a visitor func which receives a route(readonly).
// That visitor func accepts the next route of each of the route entries.
func (r *routeRepository) Visit(visitor func(RouteInfo)) {
for i := range r.routes {
visitor(r.routes[i])
}
}
// sort sorts routes by subdomain.
func (r *routeRepository) sort() {
sort.Sort(r)
}

Some files were not shown because too many files have changed in this diff Show More