mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
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:
parent
2b2a205e63
commit
244a59e055
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,4 +1,4 @@
|
|||
- Version : **6.1.2**
|
||||
- Version : **6.2.0**
|
||||
|
||||
- Operating System:
|
||||
|
||||
|
|
61
.github/PULL_REQUEST_TEMPLATE.md
vendored
61
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -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
2
.gitignore
vendored
|
@ -1,2 +1,4 @@
|
|||
.settings
|
||||
.project
|
||||
2M_donation_timeline.cpd
|
||||
performance_tips.txt
|
||||
|
|
|
@ -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
1002
HISTORY.md
File diff suppressed because it is too large
Load Diff
64
README.md
64
README.md
|
@ -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
19
adaptors/cors/LICENSE
Normal 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
33
adaptors/cors/cors.go
Normal 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{}) }
|
28
adaptors/gorillamux/LICENSE
Normal file
28
adaptors/gorillamux/LICENSE
Normal 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.
|
103
adaptors/gorillamux/README.md
Normal file
103
adaptors/gorillamux/README.md
Normal 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.
|
150
adaptors/gorillamux/gorillamux.go
Normal file
150
adaptors/gorillamux/gorillamux.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
47
adaptors/httprouter/LICENSE
Normal file
47
adaptors/httprouter/LICENSE
Normal 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.
|
39
adaptors/httprouter/_example/main.go
Normal file
39
adaptors/httprouter/_example/main.go
Normal 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")
|
||||
}
|
725
adaptors/httprouter/httprouter.go
Normal file
725
adaptors/httprouter/httprouter.go
Normal 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(
|
||||
"&", "&",
|
||||
"<", "<",
|
||||
">", ">",
|
||||
// """ is shorter than """.
|
||||
`"`, """,
|
||||
// "'" is shorter than "'" and apos was not in HTML until HTML5.
|
||||
"'", "'",
|
||||
)
|
||||
|
||||
// HTMLEscape returns a string which has no valid html code
|
||||
func HTMLEscape(s string) string {
|
||||
return htmlReplacer.Replace(s)
|
||||
}
|
23
adaptors/httprouter/urlpath.go
Normal file
23
adaptors/httprouter/urlpath.go
Normal 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
|
||||
}
|
21
adaptors/typescript/LICENSE
Normal file
21
adaptors/typescript/LICENSE
Normal 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.
|
89
adaptors/typescript/README.md
Normal file
89
adaptors/typescript/README.md
Normal 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
|
31
adaptors/typescript/_example/main.go
Normal file
31
adaptors/typescript/_example/main.go
Normal 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.
|
8
adaptors/typescript/_example/www/index.html
Normal file
8
adaptors/typescript/_example/www/index.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Load my script (lawl)</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="scripts/app.js"></script>
|
||||
</body>
|
||||
</html>
|
16
adaptors/typescript/_example/www/scripts/app.ts
Normal file
16
adaptors/typescript/_example/www/scripts/app.ts
Normal 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);
|
192
adaptors/typescript/config.go
Normal file
192
adaptors/typescript/config.go
Normal 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
|
||||
}
|
21
adaptors/typescript/editor/LICENSE
Normal file
21
adaptors/typescript/editor/LICENSE
Normal 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.
|
35
adaptors/typescript/editor/_example/main.go
Normal file
35
adaptors/typescript/editor/_example/main.go
Normal 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")
|
||||
}
|
8
adaptors/typescript/editor/_example/www/index.html
Normal file
8
adaptors/typescript/editor/_example/www/index.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Load my script (lawl)</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="scripts/app.js"></script>
|
||||
</body>
|
||||
</html>
|
16
adaptors/typescript/editor/_example/www/scripts/app.ts
Normal file
16
adaptors/typescript/editor/_example/www/scripts/app.ts
Normal 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);
|
|
@ -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"
|
||||
]
|
||||
}
|
78
adaptors/typescript/editor/config.go
Normal file
78
adaptors/typescript/editor/config.go
Normal 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
|
||||
}
|
216
adaptors/typescript/editor/editor.go
Normal file
216
adaptors/typescript/editor/editor.go
Normal 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)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package utils // #nosec
|
||||
package npm // #nosec
|
||||
|
||||
import (
|
||||
"fmt"
|
124
adaptors/typescript/npm/npm.go
Normal file
124
adaptors/typescript/npm/npm.go
Normal 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)
|
||||
}
|
236
adaptors/typescript/typescript.go
Normal file
236
adaptors/typescript/typescript.go
Normal 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
|
||||
}
|
||||
|
||||
//
|
||||
//
|
93
adaptors/view/_examples/overview/main.go
Normal file
93
adaptors/view/_examples/overview/main.go
Normal 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")
|
||||
}
|
8
adaptors/view/_examples/overview/templates/hi.html
Normal file
8
adaptors/view/_examples/overview/templates/hi.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Hi Iris</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi {{.Name}} </h1>
|
||||
</body>
|
||||
</html>
|
237
adaptors/view/_examples/template_binary/bindata.go
Normal file
237
adaptors/view/_examples/template_binary/bindata.go
Normal 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, "/")...)...)
|
||||
}
|
24
adaptors/view/_examples/template_binary/main.go
Normal file
24
adaptors/view/_examples/template_binary/main.go
Normal 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"})
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Hi Iris [THE TITLE]</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi {{.Name}}
|
||||
</body>
|
||||
</html>
|
23
adaptors/view/_examples/template_html_0/main.go
Normal file
23
adaptors/view/_examples/template_html_0/main.go
Normal 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"})
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Hi Iris</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi {{.Name}} </h1>
|
||||
</body>
|
||||
</html>
|
32
adaptors/view/_examples/template_html_1/main.go
Normal file
32
adaptors/view/_examples/template_html_1/main.go
Normal 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")
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>My Layout</title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<h1>Body is:</h1>
|
||||
<!-- Render the current template here -->
|
||||
{{ yield }}
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,4 @@
|
|||
<h1>
|
||||
Title: {{.Title}}
|
||||
</h1>
|
||||
<h3>Message: {{.Message}} </h3>
|
3
adaptors/view/_examples/template_html_2/README.md
Normal file
3
adaptors/view/_examples/template_html_2/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Info
|
||||
|
||||
This folder examines the {{render "dir/templatefilename"}} functionality to manually render any template inside any template
|
49
adaptors/view/_examples/template_html_2/main.go
Normal file
49
adaptors/view/_examples/template_html_2/main.go
Normal 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")
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,7 @@
|
|||
<div style="background-color: black; color: blue">
|
||||
|
||||
<h1>Page 1 {{ greet "iris developer"}}</h1>
|
||||
|
||||
{{ render "partials/page1_partial1.html"}}
|
||||
|
||||
</div>
|
|
@ -0,0 +1,3 @@
|
|||
<div style="background-color: white; color: red">
|
||||
<h1>Page 1's Partial 1</h1>
|
||||
</div>
|
53
adaptors/view/_examples/template_html_3/main.go
Normal file
53
adaptors/view/_examples/template_html_3/main.go
Normal 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())
|
||||
}
|
27
adaptors/view/_examples/template_html_3/templates/page.html
Normal file
27
adaptors/view/_examples/template_html_3/templates/page.html
Normal 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>
|
32
adaptors/view/_examples/template_html_4/hosts
Normal file
32
adaptors/view/_examples/template_html_4/hosts
Normal 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
|
53
adaptors/view/_examples/template_html_4/main.go
Normal file
53
adaptors/view/_examples/template_html_4/main.go
Normal 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())
|
||||
}
|
16
adaptors/view/_examples/template_html_4/templates/page.html
Normal file
16
adaptors/view/_examples/template_html_4/templates/page.html
Normal 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
136
adaptors/view/adaptor.go
Normal 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
48
adaptors/view/amber.go
Normal 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
93
adaptors/view/django.go
Normal 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
|
||||
}
|
62
adaptors/view/handlebars.go
Normal file
62
adaptors/view/handlebars.go
Normal 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
92
adaptors/view/html.go
Normal 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
21
adaptors/view/pug.go
Normal 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
299
addr.go
Normal 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() }
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
349
context.go
349
context.go
|
@ -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
|
||||
|
|
827
context_test.go
827
context_test.go
|
@ -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
521
doc.go
Normal 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"
|
|
@ -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.
|
|
@ -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.
|
|
@ -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`.
|
|
@ -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
159
handler.go
Normal 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
|
||||
}
|
792
http_test.go
792
http_test.go
|
@ -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)
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package main // import "github.com/kataras/iris/iris"
|
||||
package main
|
||||
|
||||
/*
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"github.com/kataras/cli"
|
||||
"github.com/kataras/iris"
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -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).
|
56
middleware/basicauth/_example/main.go
Normal file
56
middleware/basicauth/_example/main.go
Normal 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")
|
||||
}
|
131
middleware/basicauth/basicauth.go
Normal file
131
middleware/basicauth/basicauth.go
Normal 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
|
||||
}
|
||||
|
||||
}
|
48
middleware/basicauth/config.go
Normal file
48
middleware/basicauth/config.go
Normal 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
167
middleware/i18n/LICENSE
Normal 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
69
middleware/i18n/README.md
Normal 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)
|
1
middleware/i18n/_example/locales/locale_el-GR.ini
Normal file
1
middleware/i18n/_example/locales/locale_el-GR.ini
Normal file
|
@ -0,0 +1 @@
|
|||
hi = Γεια, %s
|
1
middleware/i18n/_example/locales/locale_en-US.ini
Normal file
1
middleware/i18n/_example/locales/locale_en-US.ini
Normal file
|
@ -0,0 +1 @@
|
|||
hi = hello, %s
|
1
middleware/i18n/_example/locales/locale_zh-CN.ini
Normal file
1
middleware/i18n/_example/locales/locale_zh-CN.ini
Normal file
|
@ -0,0 +1 @@
|
|||
hi = 您好,%s
|
50
middleware/i18n/_example/main.go
Normal file
50
middleware/i18n/_example/main.go
Normal 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
18
middleware/i18n/config.go
Normal 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
101
middleware/i18n/i18n.go
Normal 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
|
||||
}
|
53
middleware/logger/_example/main.go
Normal file
53
middleware/logger/_example/main.go
Normal 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")
|
||||
|
||||
}
|
21
middleware/logger/config.go
Normal file
21
middleware/logger/config.go
Normal 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}
|
||||
}
|
62
middleware/logger/logger.go
Normal file
62
middleware/logger/logger.go
Normal 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
|
||||
}
|
31
middleware/recover/_example/main.go
Normal file
31
middleware/recover/_example/main.go
Normal 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")
|
||||
}
|
58
middleware/recover/recover.go
Normal file
58
middleware/recover/recover.go
Normal 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
615
plugin.go
|
@ -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
|
||||
}
|
205
plugin_test.go
205
plugin_test.go
|
@ -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!")
|
||||
}
|
||||
|
||||
}
|
|
@ -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
432
policy.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
332
route.go
Normal 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
Loading…
Reference in New Issue
Block a user