Version 3.0.0-beta cleaned

This commit is contained in:
Makis Maropoulos 2016-05-30 17:08:09 +03:00
commit c26668a489
114 changed files with 14552 additions and 0 deletions

25
.gitignore vendored Normal file
View File

@ -0,0 +1,25 @@
.project
.idea
.git
.settings
.vscode/*
.atom-build.json
github/*
iris.test.exe
cpu_test.out
mem_test.out
cover_test.out
block_test.out
heap_test.out
cpu_test.txt
cover_profile_test.txt
build.bat
_examples/*.o
_examples/*.a
_examples/*.so
*.o
*.a
*.so
build.bat
tools/*
docs/*

11
.travis.yml Normal file
View File

@ -0,0 +1,11 @@
language: go
go:
- go1.6
- tip
script:
- go test -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)

32
DONATIONS.md Normal file
View File

@ -0,0 +1,32 @@
I spend all my time in the construction of Iris, therefore I have no income value.
I cannot support this project alone for a long period without your support.
If you,
- think that any information you obtained here is worth some money
- believe that Iris worths to remains a highly active project
feel free to send any amount through paypal
[![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=makis%40ideopod%2ecom&lc=GR&item_name=Iris%20web%20framework&item_number=iriswebframeworkdonationid2016&amount=2%2e00&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)
### More about your donations
**Thank you**!
I'm grateful for all the generous donations. Iris is fully funded by these donations.
#### Donors
- [Ryan Brooks](https://github.com/ryanbyyc) donated 50 EUR at May 11
> The name of the donator added after his/her permission.
#### Report, so far
- 13 EUR for the domain, [iris-go.com](https://iris-go.com)
**Available**: VAT(50)-13 = 47.5-13 = 34.5 EUR

283
HISTORY.md Normal file
View File

@ -0,0 +1,283 @@
# History
## 3.0.0-alpha.beta -> 3.0.0-beta
- New [iris.API] for easy API declaration, read more [here](https://kataras.gitbooks.io/iris/content/using-handlerapi.html), example [there](https://github.com/iris-contrib/examples/tree/master/api_handler_2).
- Add [example](https://github.com/iris-contrib/examples/tree/master/middleware_basicauth_2) and fix the Basic Authentication middleware
## 3.0.0-alpha.6 -> 3.0.0-alpha.beta
- [Implement feature request to add Globals on the pongo2](https://github.com/kataras/iris/issues/145)
- [Implement feature request for static Favicon ](https://github.com/kataras/iris/issues/141)
- Implement a unique easy only-websocket support:
```go
OnConnection(func(c websocket.Connection){})
```
websocket.Connection
```go
// 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 via native websocket way, compatible without need of import the iris-ws.js to the .html
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(c)
To(websocket.NotMe).Emit/EmitMessage...
// Rooms, group of connections/clients
Join("anyCustomRoom")
Leave("anyCustomRoom")
// Fired when the connection is closed
OnDisconnect(func(){})
```
- [Example](https://github.com/iris-contrib/examples/tree/master/websocket)
- [E-book section](https://kataras.gitbooks.io/iris/content/package-websocket.html)
We have some base-config's changed, these configs which are defaulted to true renamed to 'Disable+$oldName'
```go
// DisablePathCorrection corrects and redirects the requested path to the registed path
// for example, if /home/ path is requested but no handler for this Route found,
// then the Router checks if /home handler exists, if yes,
// (permant)redirects the client to the correct path /home
//
// Default is false
DisablePathCorrection bool
// DisablePathEscape when is false then its escapes the path, the named parameters (if any).
// Change to true it if you want something like this https://github.com/kataras/iris/issues/135 to work
//
// When do you need to Disable(true) it:
// accepts parameters with slash '/'
// Request: http://localhost:8080/details/Project%2FDelta
// ctx.Param("project") returns the raw named parameter: Project%2FDelta
// which you can escape it manually with net/url:
// projectName, _ := url.QueryUnescape(c.Param("project").
// Look here: https://github.com/kataras/iris/issues/135 for more
//
// Default is false
DisablePathEscape bool
// DisableLog turn it to true if you want to disable logger,
// Iris prints/logs ONLY errors, so be careful when you enable it
DisableLog bool
// DisableBanner outputs the iris banner at startup
//
// Default is false
DisableBanner bool
```
## 3.0.0-alpha.5 -> 3.0.0-alpha.6
Changes:
- config/iris.Config().Render.Template.HTMLTemplate.Funcs typeof `[]template.FuncMap` -> `template.FuncMap`
Added:
- iris.AmberEngine [Amber](https://github.com/eknkc/amber). [View an example](https://github.com/iris-contrib/examples/tree/master/templates_7_html_amber)
- iris.JadeEngine [Jade](https://github.com/Joker/jade). [View an example](https://github.com/iris-contrib/examples/tree/master/templates_6_html_jade)
Book section [Render/Templates updated](https://kataras.gitbooks.io/iris/content/render_templates.html)
## 3.0.0-alpha.4 -> 3.0.0-alpha.5
- [NoLayout support for particular templates](https://github.com/kataras/iris/issues/130#issuecomment-219754335)
- [Raw Markdown Template Engine](https://kataras.gitbooks.io/iris/content/render_templates.html)
- [Markdown to HTML](https://kataras.gitbooks.io/iris/content/render_rest.html) > `context.Markdown(statusCode int, markdown string)` , `context.MarkdownString(markdown string) (htmlReturn string)`
- [Simplify the plugin registration](https://github.com/kataras/iris/issues/126#issuecomment-219622481)
## 3.0.0-alpha.3 -> 3.0.0-alpha.4
Community suggestions implemented:
- [Request: Rendering html template to string](https://github.com/kataras/iris/issues/130)
> New RenderString(name string, binding interface{}, layout ...string) added to the Context & the Iris' station (iris.Templates().RenderString)
- [Minify Templates](https://github.com/kataras/iris/issues/129)
> New config field for minify, defaulted to true: iris.Config().Render.Template.Minify = true
> 3.0.0-alpha5+ this has been removed because the minify package has bugs, one of them is this: https://github.com/tdewolff/minify/issues/35.
Bugfixes and enhancements:
- [Static not allowing configuration of `IndexNames`](https://github.com/kataras/iris/issues/128)
- [Processing access error](https://github.com/kataras/iris/issues/125)
- [Invalid header](https://github.com/kataras/iris/issues/123)
## 3.0.0-alpha.2 -> 3.0.0-alpha.3
The only change here is a panic-fix on form bindings. Now **no need to make([]string,0)** before form binding, new example:
```go
//./main.go
package main
import (
"fmt"
"github.com/kataras/iris"
)
type Visitor struct {
Username string
Mail string
Data []string `form:"mydata"`
}
func main() {
iris.Get("/", func(ctx *iris.Context) {
ctx.Render("form.html", nil)
})
iris.Post("/form_action", func(ctx *iris.Context) {
visitor := Visitor{}
err := ctx.ReadForm(&visitor)
if err != nil {
fmt.Println("Error when reading form: " + err.Error())
}
fmt.Printf("\n Visitor: %v", visitor)
})
fmt.Println("Server is running at :8080")
iris.Listen(":8080")
}
```
```html
<!-- ./templates/form.html -->
<!DOCTYPE html>
<head>
<meta charset="utf-8">
</head>
<body>
<form action="/form_action" method="post">
<input type="text" name="Username" />
<br/>
<input type="text" name="Mail" /><br/>
<select multiple="multiple" name="mydata">
<option value='one'>One</option>
<option value='two'>Two</option>
<option value='three'>Three</option>
<option value='four'>Four</option>
</select>
<hr/>
<input type="submit" value="Send data" />
</form>
</body>
</html>
```
## 3.0.0-alpha.1 -> 3.0.0-alpha.2
*The e-book was updated, take a closer look [here](https://www.gitbook.com/book/kataras/iris/details)*
**Breaking changes**
**First**. Configuration owns a package now `github.com/kataras/iris/config` . I took this decision after a lot of thought and I ensure you that this is the best
architecture to easy:
- change the configs without need to re-write all of their fields.
```go
irisConfig := config.Iris { Profile: true, PathCorrection: false }
api := iris.New(irisConfig)
```
- easy to remember: `iris` type takes config.Iris, sessions takes config.Sessions`, `iris.Config().Render` is `config.Render`, `iris.Config().Render.Template` is `config.Template`, `Logger` takes `config.Logger` and so on...
- easy to find what features are exists and what you can change: just navigate to the config folder and open the type you want to learn about, for example `/iris.go` Iris' type configuration is on `/config/iris.go`
- default setted fields which you can use. They are already setted by iris, so don't worry too much, but if you ever need them you can find their default configs by this pattern: for example `config.Template` has `config.DefaultTemplate()`, `config.Rest` has `config.DefaultRest()`, `config.Typescript()` has `config.DefaultTypescript()`, note that only `config.Iris` has `config.Default()`. I wrote that all structs even the plugins have their default configs now, to make it easier for you, so you can do this without set a config by yourself: `iris.Config().Render.Template.Engine = config.PongoEngine` or `iris.Config().Render.Template.Pongo.Extensions = []string{".xhtml", ".html"}`.
**Second**. Template & rest package moved to the `render`, so
* a new config field named `render` of type `config.Render` which nests the `config.Template` & `config.Rest`
- `iris.Config().Templates` -> `iris.Config().Render.Template` of type `config.Template`
- `iris.Config().Rest` -> `iris.Config().Render.Rest` of type `config.Rest`
**Third, sessions**.
Configuration instead of parameters. Before `sessions.New("memory","sessionid",time.Duration(42) * time.Minute)` -> Now: `sessions.New(config.DefaultSessions())` of type `config.Sessions`
- Before this change the cookie's life was the same as the manager's Gc duration. Now added an Expires option for the cookie's life time which defaults to infinitive, as you (correctly) suggests me in the chat community.-
- Default Cookie's expiration date: from 42 minutes -> to `infinitive/forever`
- Manager's Gc duration: from 42 minutes -> to '2 hours'
- Redis store's MaxAgeSeconds: from 42 minutes -> to '1 year`
**Four**. Typescript, Editor & IrisControl plugins now accept a config.Typescript/ config.Editor/ config.IrisControl as parameter
Bugfixes
- [can't open /xxx/ path when PathCorrection = false ](https://github.com/kataras/iris/issues/120)
- [Invalid content on links on debug page when custom ProfilePath is set](https://github.com/kataras/iris/issues/118)
- [Example with custom config not working ](https://github.com/kataras/iris/issues/115)
- [Debug Profiler writing escaped HTML?](https://github.com/kataras/iris/issues/107)
- [CORS middleware doesn't work](https://github.com/kataras/iris/issues/108)
## 2.3.2 -> 3.0.0-alpha.1
**Changed**
- `&render.Config` -> `&iris.RestConfig` . All related to the html/template are removed from there.
- `ctx.Render("index",...)` -> `ctx.Render("index.html",...)` or any extension you have defined in iris.Config().Templates.Extensions
- `iris.Config().Render.Layout = "layouts/layout"` -> `iris.Config().Templates.Layout = "layouts/layout.html"`
- `License BSD-3 Clause Open source` -> `MIT License`
**Added**
- Switch template engines via `IrisConfig`. Currently, HTMLTemplate is 'html/template'. Pongo is 'flosch/pongo2`. Refer to the Book, which is updated too, [read here](https://kataras.gitbooks.io/iris/content/render.html).

178
LICENSE Normal file
View File

@ -0,0 +1,178 @@
Copyright (c) 2016, Gerasimos Maropoulos
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:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) 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
(d) 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

150
README.md Normal file
View File

@ -0,0 +1,150 @@
[![Iris Logo](http://iris-go.com/assets/iris_full_logo_2.png)](http://iris-go.com)
[![Travis Widget]][Travis] [![Release Widget]][Release] [![Report Widget]][Report] [![License Widget]][License] [![Gitter Widget]][Gitter] [![Documentation Widget]][Documentation]
[Travis Widget]: https://img.shields.io/travis/tmrts/boilr.svg?style=flat-square
[Travis]: http://travis-ci.org/kataras/iris
[License Widget]: https://img.shields.io/badge/license-Apache%20License%202.0-E91E63.svg?style=flat-square
[License]: https://github.com/kataras/iris/blob/master/LICENSE
[Release Widget]: https://img.shields.io/badge/release-v3.0.0--beta-blue.svg?style=flat-square
[Release]: https://github.com/kataras/iris/releases
[Gitter Widget]: https://img.shields.io/badge/chat-on%20gitter-00BCD4.svg?style=flat-square
[Gitter]: https://gitter.im/kataras/iris
[Report Widget]: https://img.shields.io/badge/report%20card-A%2B-F44336.svg?style=flat-square
[Report]: http://goreportcard.com/report/kataras/iris
[Documentation Widget]: https://img.shields.io/badge/documentation-reference-5272B4.svg?style=flat-square
[Documentation]: https://www.gitbook.com/book/kataras/iris/details
[Language Widget]: https://img.shields.io/badge/powered_by-Go-3362c2.svg?style=flat-square
[Language]: http://golang.org
[Platform Widget]: https://img.shields.io/badge/platform-Any--OS-gray.svg?style=flat-square
[![Benchmark Wizzard Processing Time Horizontal Graph](https://raw.githubusercontent.com/iris-contrib/website/cf71811e6acb2f9bf1e715e25660392bf090b923/assets/benchmark_horizontal_transparent.png)](#benchmarks)
```sh
$ cat main.go
```
```go
package main
import "github.com/kataras/iris"
func main() {
iris.Get("/hi_json", func(c *iris.Context) {
c.JSON(200, iris.Map{
"Name": "Iris",
"Age": 2,
})
})
iris.Listen(":8080")
}
```
> Learn about [configuration](https://kataras.gitbooks.io/iris/content/configuration.html) and [render](https://kataras.gitbooks.io/iris/content/render.html).
Installation
------------
The only requirement is Go 1.6
`$ go get -u github.com/kataras/iris/iris`
>If you are connected to the Internet through China [click here](https://kataras.gitbooks.io/iris/content/install.html)
Features
------------
- Focus on high performance
- Robust routing & subdomains
- View system supporting [5+](https://kataras.gitbooks.io/iris/content/render_templates.html) template engines
- Highly scalable Websocket API with custom events
- Sessions support with GC, memory & redis providers
- Middlewares & Plugins were never be easier
- Full REST API
- Custom HTTP Errors
- Typescript compiler + Browser editor
- Content negotiation & streaming
- Transport Layer Security
Docs & Community
------------
<a href="https://www.gitbook.com/book/kataras/iris/details"><img align="right" width="185" src="http://iris-go.com/assets/book/cover_1.png"></a>
- Read the [book](https://www.gitbook.com/book/kataras/iris/details) or [wiki](https://github.com/kataras/iris/wiki)
- Take a look at the [examples](https://github.com/iris-contrib/examples)
If you'd like to discuss this package, or ask questions about it, feel free to
* Post an issue or idea [here](https://github.com/kataras/iris/issues)
* [Chat]( https://gitter.im/kataras/iris) with us
Open debates
- [E-book Cover - Which one you suggest?](https://github.com/kataras/iris/issues/67)
**TIP** Be sure to read the [history](HISTORY.md) for Migrating from 2.x to 3.x.
Philosophy
------------
The Iris philosophy is to provide robust tooling for HTTP, making it a great solution for single page applications, web sites, hybrids, or public HTTP APIs.
Iris does not force you to use any specific ORM or template engine. With support for the most used template engines, you can quickly craft the perfect application.
Benchmarks
------------
[This Benchmark suite](https://github.com/smallnest/go-web-framework-benchmark) aims to compare the whole HTTP request processing between Go web frameworks.
![Benchmark Wizzard Processing Time Horizontal Graph](https://raw.githubusercontent.com/iris-contrib/website/cf71811e6acb2f9bf1e715e25660392bf090b923/assets/benchmark_horizontal_transparent.png)
[Please click here to view all detailed benchmarks.](https://github.com/smallnest/go-web-framework-benchmark)
Testing
------------
Iris suggests you to use [this](https://github.com/gavv/httpexpect) new suite to test your API.
[Httpexpect](https://github.com/gavv/httpexpect) supports fasthttp & Iris after [recommandation](https://github.com/gavv/httpexpect/issues/2). Its author is very active so I believe its a promising library. You can view examples [here](https://github.com/gavv/httpexpect/blob/master/example/iris_test.go) and [here](https://github.com/kataras/iris/blob/master/tests/router_test.go).
Versioning
------------
Current: **v3.0.0-beta**
> Iris is an active project
Read more about Semantic Versioning 2.0.0
- http://semver.org/
- https://en.wikipedia.org/wiki/Software_versioning
- https://wiki.debian.org/UpstreamGuide#Releases_and_Versions
Todo
------------
> for the next release 'v3'
- [x] Create server & client side (js) library for .on('event', func action(...)) / .emit('event')... (like socket.io but supports only websocket).
- [x] Find and provide support for the most stable template engine and be able to change it via the configuration, keep html/templates support.
- [x] Extend, test and publish to the public the [Iris' cmd](https://github.com/kataras/iris/tree/master/iris).
If you're willing to donate click [here](DONATIONS.md)
People
------------
The author of Iris is [@kataras](https://github.com/kataras)
License
------------
This project is licensed under the Apache License 2.0.
License can be found [here](https://github.com/kataras/iris/blob/master/LICENSE).

13
THIRDPARTY.md Normal file
View File

@ -0,0 +1,13 @@
Third party packages
------------
- [Iris is build on top of fasthttp](https://github.com/valyala/fasthttp)
- [pongo2 is one of the supporting template engines](https://github.com/flosch/pongo2)
- [amber is one of the supporting template engines](https://github.com/eknkc/amber)
- [jade is one of the supporting template engines](https://github.com/Joker/jade)
- [blackfriday is one of the supporting template engines](https://github.com/russross/blackfriday)
- [klauspost/gzip for faster compression](https://github.com/klauspost/compress/gzip)
- [mergo for merge configs](https://github.com/imdario/mergo)
- [formam as form binder](https://github.com/monoculum/formam)
- [i18n for internalization](https://github.com/Unknwon/i18n)
- [color for banner](https://github.com/fatih/color)

14
bindings/errors.go Normal file
View File

@ -0,0 +1,14 @@
package bindings
import "github.com/kataras/iris/errors"
var (
// ErrNoForm returns an error with message: 'Request has no any valid form'
ErrNoForm = errors.New("Request has no any valid form")
// ErrWriteJSON returns an error with message: 'Before JSON be written to the body, JSON Encoder returned an error. Trace: +specific error'
ErrWriteJSON = errors.New("Before JSON be written to the body, JSON Encoder returned an error. Trace: %s")
// ErrRenderMarshalled returns an error with message: 'Before +type Rendering, MarshalIndent retured an error. Trace: +specific error'
ErrRenderMarshalled = errors.New("Before +type Rendering, MarshalIndent returned an error. Trace: %s")
// ErrReadBody returns an error with message: 'While trying to read +type from the request body. Trace +specific error'
ErrReadBody = errors.New("While trying to read %s from the request body. Trace %s")
)

420
bindings/form.go Normal file
View File

@ -0,0 +1,420 @@
/*
File bindings/form.go source code from https://github.com/monoculum/formame.
*/
package bindings
import (
"encoding"
"errors"
"fmt"
"net/url"
"reflect"
"strconv"
"strings"
"time"
"github.com/kataras/iris/context"
)
const tagName = "form"
// A pathMap holds the values of a map with its key and values correspondent
type pathMap struct {
m reflect.Value
key string
value reflect.Value
path string
}
// a pathMaps holds the values for each key
type pathMaps []*pathMap
// find find and get the value by the given key
func (ma pathMaps) find(id reflect.Value, key string) *pathMap {
for _, v := range ma {
if v.m == id && v.key == key {
return v
}
}
return nil
}
// A decoder holds the values from form, the 'reflect' value of main struct
// and the 'reflect' value of current path
type decoder struct {
main reflect.Value
curr reflect.Value
value string
values []string
path string
field string
index int
maps pathMaps
}
// Decode decodes the url.Values into a element that must be a pointer to a type provided by argument
func Decode(vs url.Values, dst interface{}) error {
main := reflect.ValueOf(dst)
if main.Kind() != reflect.Ptr {
return fmt.Errorf(tagName+": the value passed for decode is not a pointer but a %v", main.Kind())
}
dec := &decoder{main: main.Elem()}
// iterate over the form's values and decode it
for k, v := range vs {
dec.path = k
dec.field = k
dec.values = v
dec.value = v[0]
if dec.value != "" {
if err := dec.begin(); err != nil {
return err
}
}
}
// set values of each maps
for _, v := range dec.maps {
key := v.m.Type().Key()
switch key.Kind() {
case reflect.String:
// the key is a string
v.m.SetMapIndex(reflect.ValueOf(v.key), v.value)
default:
// must to implement the TextUnmarshaler interface for to can to decode the map's key
var val reflect.Value
if key.Kind() == reflect.Ptr {
val = reflect.New(key.Elem())
} else {
val = reflect.New(key).Elem()
}
dec.value = v.key
if ok, err := dec.unmarshalText(val); !ok {
return fmt.Errorf(tagName+": the key with %s type (%v) in the path %v should implements the TextUnmarshaler interface for to can decode it", key, v.m.Type(), v.path)
} else if err != nil {
return fmt.Errorf(tagName+": an error has occured in the UnmarshalText method for type %s: %s", key, err)
}
v.m.SetMapIndex(val, v.value)
}
}
dec.maps = make(pathMaps, 0)
return nil
}
// begin prepare the current path to walk through it
func (dec *decoder) begin() (err error) {
dec.curr = dec.main
fields := strings.Split(dec.field, ".")
for i, field := range fields {
b := strings.IndexAny(field, "[")
if b != -1 {
// is a array
e := strings.IndexAny(field, "]")
if e == -1 {
return errors.New(tagName + ": bad syntax array")
}
dec.field = field[:b]
if dec.index, err = strconv.Atoi(field[b+1 : e]); err != nil {
return errors.New(tagName + ": the index of array is not a number")
}
if len(fields) == i+1 {
return dec.end()
}
if err = dec.walk(); err != nil {
return
}
} else {
// not is a array
dec.field = field
dec.index = -1
if len(fields) == i+1 {
return dec.end()
}
if err = dec.walk(); err != nil {
return
}
}
}
return
}
// walk traverses the current path until to the last field
func (dec *decoder) walk() error {
// check if is a struct or map
switch dec.curr.Kind() {
case reflect.Struct:
if err := dec.findStructField(); err != nil {
return err
}
case reflect.Map:
dec.currentMap()
}
// check if the struct or map is a interface
if dec.curr.Kind() == reflect.Interface {
dec.curr = dec.curr.Elem()
}
// check if the struct or map is a pointer
if dec.curr.Kind() == reflect.Ptr {
if dec.curr.IsNil() {
dec.curr.Set(reflect.New(dec.curr.Type().Elem()))
}
dec.curr = dec.curr.Elem()
}
// finally, check if there are access to slice/array or not...
if dec.index != -1 {
switch dec.curr.Kind() {
case reflect.Slice, reflect.Array:
if dec.curr.Len() <= dec.index {
dec.expandSlice(dec.index + 1)
}
dec.curr = dec.curr.Index(dec.index)
default:
return fmt.Errorf(tagName+": the field \"%v\" in path \"%v\" has a index for array but it is not", dec.field, dec.path)
}
}
return nil
}
// end finds the last field for decode its value correspondent
func (dec *decoder) end() error {
if dec.curr.Kind() == reflect.Struct {
if err := dec.findStructField(); err != nil {
return err
}
}
if dec.value == "" {
return nil
}
return dec.decode()
}
// decode sets the value in the last field found by end function
func (dec *decoder) decode() error {
if ok, err := dec.unmarshalText(dec.curr); ok || err != nil {
return err
}
switch dec.curr.Kind() {
case reflect.Map:
dec.currentMap()
return dec.decode()
case reflect.Slice, reflect.Array:
if dec.index == -1 {
// not has index, so to decode all values in the slice/array
dec.expandSlice(len(dec.values))
tmp := dec.curr
for i, v := range dec.values {
dec.curr = tmp.Index(i)
dec.value = v
if err := dec.decode(); err != nil {
return err
}
}
} else {
// has index, so to decode value by index indicated
if dec.curr.Len() <= dec.index {
dec.expandSlice(dec.index + 1)
}
dec.curr = dec.curr.Index(dec.index)
return dec.decode()
}
case reflect.String:
dec.curr.SetString(dec.value)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if num, err := strconv.ParseInt(dec.value, 10, 64); err != nil {
return fmt.Errorf(tagName+": the value of field \"%v\" in path \"%v\" should be a valid signed integer number", dec.field, dec.path)
} else {
dec.curr.SetInt(num)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if num, err := strconv.ParseUint(dec.value, 10, 64); err != nil {
return fmt.Errorf(tagName+": the value of field \"%v\" in path \"%v\" should be a valid unsigned integer number", dec.field, dec.path)
} else {
dec.curr.SetUint(num)
}
case reflect.Float32, reflect.Float64:
if num, err := strconv.ParseFloat(dec.value, dec.curr.Type().Bits()); err != nil {
return fmt.Errorf(tagName+": the value of field \"%v\" in path \"%v\" should be a valid float number", dec.field, dec.path)
} else {
dec.curr.SetFloat(num)
}
case reflect.Bool:
switch dec.value {
case "true", "on", "1":
dec.curr.SetBool(true)
case "false", "off", "0":
dec.curr.SetBool(false)
default:
return fmt.Errorf(tagName+": the value of field \"%v\" in path \"%v\" is not a valid boolean", dec.field, dec.path)
}
case reflect.Interface:
dec.curr.Set(reflect.ValueOf(dec.value))
case reflect.Ptr:
dec.curr.Set(reflect.New(dec.curr.Type().Elem()))
dec.curr = dec.curr.Elem()
return dec.decode()
case reflect.Struct:
switch dec.curr.Interface().(type) {
case time.Time:
t, err := time.Parse("2006-01-02", dec.value)
if err != nil {
return fmt.Errorf(tagName+": the value of field \"%v\" in path \"%v\" is not a valid datetime", dec.field, dec.path)
}
dec.curr.Set(reflect.ValueOf(t))
case url.URL:
u, err := url.Parse(dec.value)
if err != nil {
return fmt.Errorf(tagName+": the value of field \"%v\" in path \"%v\" is not a valid url", dec.field, dec.path)
}
dec.curr.Set(reflect.ValueOf(*u))
default:
return fmt.Errorf(tagName+": not supported type for field \"%v\" in path \"%v\"", dec.field, dec.path)
}
default:
return fmt.Errorf(tagName+": not supported type for field \"%v\" in path \"%v\"", dec.field, dec.path)
}
return nil
}
// findField finds a field by its name, if it is not found,
// then retry the search examining the tag "form" of every field of struct
func (dec *decoder) findStructField() error {
var anon reflect.Value
num := dec.curr.NumField()
for i := 0; i < num; i++ {
field := dec.curr.Type().Field(i)
if field.Name == dec.field {
// check if the field's name is equal
dec.curr = dec.curr.Field(i)
return nil
} else if field.Anonymous {
// if the field is a anonymous struct, then iterate over its fields
tmp := dec.curr
dec.curr = dec.curr.FieldByIndex(field.Index)
if err := dec.findStructField(); err != nil {
dec.curr = tmp
continue
}
// field in anonymous struct is found,
// but first it should found the field in the rest of struct
// (a field with same name in the current struct should have preference over anonymous struct)
anon = dec.curr
dec.curr = tmp
} else if dec.field == field.Tag.Get(tagName) {
dec.curr = dec.curr.Field(i)
return nil
}
}
if anon.IsValid() {
dec.curr = anon
return nil
}
return fmt.Errorf(tagName+": not found the field \"%v\" in the path \"%v\"", dec.field, dec.path)
}
// expandSlice expands the length and capacity of the current slice
func (dec *decoder) expandSlice(length int) {
n := reflect.MakeSlice(dec.curr.Type(), length, length)
reflect.Copy(n, dec.curr)
dec.curr.Set(n)
}
// currentMap gets in d.curr the map concrete for decode the current value
func (dec *decoder) currentMap() {
n := dec.curr.Type()
if dec.curr.IsNil() {
dec.curr.Set(reflect.MakeMap(n))
m := reflect.New(n.Elem()).Elem()
dec.maps = append(dec.maps, &pathMap{dec.curr, dec.field, m, dec.path})
dec.curr = m
} else if a := dec.maps.find(dec.curr, dec.field); a == nil {
m := reflect.New(n.Elem()).Elem()
dec.maps = append(dec.maps, &pathMap{dec.curr, dec.field, m, dec.path})
dec.curr = m
} else {
dec.curr = a.value
}
}
var (
timeType = reflect.TypeOf(time.Time{})
timePType = reflect.TypeOf(&time.Time{})
)
// unmarshalText returns a boolean and error. The boolean is true if the
// value implements TextUnmarshaler, and false if not.
func (dec *decoder) unmarshalText(v reflect.Value) (bool, error) {
// skip if the type is time.Time
n := v.Type()
if n.ConvertibleTo(timeType) || n.ConvertibleTo(timePType) {
return false, nil
}
// check if implements the interface
m, ok := v.Interface().(encoding.TextUnmarshaler)
addr := v.CanAddr()
if !ok && !addr {
return false, nil
} else if addr {
return dec.unmarshalText(v.Addr())
}
// return result
err := m.UnmarshalText([]byte(dec.value))
return true, err
}
// BindForm binds the formObject with the form data
// it supports any kind of struct
func BindForm(ctx context.IContext, formObject interface{}) error {
reqCtx := ctx.GetRequestCtx()
// first check if we have multipart form
form, err := reqCtx.MultipartForm()
if err == nil {
//we have multipart form
return ErrReadBody.With(Decode(form.Value, formObject))
}
// if no multipart and post arguments ( means normal form)
if reqCtx.PostArgs().Len() > 0 {
form := make(map[string][]string, reqCtx.PostArgs().Len()+reqCtx.QueryArgs().Len())
reqCtx.PostArgs().VisitAll(func(k []byte, v []byte) {
key := string(k)
value := string(v)
// for slices
if form[key] != nil {
form[key] = append(form[key], value)
} else {
form[key] = []string{value}
}
})
reqCtx.QueryArgs().VisitAll(func(k []byte, v []byte) {
key := string(k)
value := string(v)
// for slices
if form[key] != nil {
form[key] = append(form[key], value)
} else {
form[key] = []string{value}
}
})
return ErrReadBody.With(Decode(form, formObject))
}
return ErrReadBody.With(ErrNoForm.Return())
}

25
bindings/json.go Normal file
View File

@ -0,0 +1,25 @@
package bindings
import (
"encoding/json"
"io"
"strings"
"github.com/kataras/iris/context"
)
// BindJSON reads JSON from request's body
func BindJSON(ctx context.IContext, jsonObject interface{}) error {
data := ctx.GetRequestCtx().Request.Body()
decoder := json.NewDecoder(strings.NewReader(string(data)))
err := decoder.Decode(jsonObject)
//err != nil fix by @shiena
if err != nil && err != io.EOF {
return ErrReadBody.Format("JSON", err.Error())
}
return nil
}

23
bindings/xml.go Normal file
View File

@ -0,0 +1,23 @@
package bindings
import (
"encoding/xml"
"io"
"strings"
"github.com/kataras/iris/context"
)
// BindXML reads XML from request's body
func BindXML(ctx context.IContext, xmlObject interface{}) error {
data := ctx.GetRequestCtx().Request.Body()
decoder := xml.NewDecoder(strings.NewReader(string(data)))
err := decoder.Decode(xmlObject)
//err != nil fix by @shiena
if err != nil && err != io.EOF {
return ErrReadBody.Format("XML", err.Error())
}
return nil
}

457
branch.go Normal file
View File

@ -0,0 +1,457 @@
// Copyright (c) 2013 Julien Schmidt, Copyright (c) 2016 Gerasimos Maropoulos,
package iris
import (
"bytes"
"strings"
"github.com/kataras/iris/utils"
)
const (
isStatic BranchCase = iota
isRoot
hasParams
matchEverything
)
type (
// PathParameter is a struct which contains Key and Value, used for named path parameters
PathParameter struct {
Key string
Value string
}
// PathParameters type for a slice of PathParameter
// Tt's a slice of PathParameter type, because it's faster than map
PathParameters []PathParameter
// BranchCase is the type which the type of Branch using in order to determinate what type (parameterized, anything, static...) is the perticular node
BranchCase uint8
// IBranch is the interface which the type Branch must implement
IBranch interface {
AddBranch(string, Middleware)
AddNode(uint8, string, string, Middleware)
GetBranch(string, PathParameters) (Middleware, PathParameters, bool)
GivePrecedenceTo(index int) int
}
// Branch is the node of a tree of the routes,
// in order to learn how this is working, google 'trie' or watch this lecture: https://www.youtube.com/watch?v=uhAUk63tLRM
// this method is used by the BSD's kernel also
Branch struct {
part string
BranchCase BranchCase
hasWildNode bool
tokens string
nodes []*Branch
middleware Middleware
precedence uint64
paramsLen uint8
}
)
var _ IBranch = &Branch{}
// Get returns a value from a key inside this Parameters
// If no parameter with this key given then it returns an empty string
func (params PathParameters) Get(key string) string {
for _, p := range params {
if p.Key == key {
return p.Value
}
}
return ""
}
// String returns a string implementation of all parameters that this PathParameters object keeps
// hasthe form of key1=value1,key2=value2...
func (params PathParameters) String() string {
var buff bytes.Buffer
for i := range params {
buff.WriteString(params[i].Key)
buff.WriteString("=")
buff.WriteString(params[i].Value)
if i < len(params)-1 {
buff.WriteString(",")
}
}
return buff.String()
}
// ParseParams receives a string and returns PathParameters (slice of PathParameter)
// received string must have this form: key1=value1,key2=value2...
func ParseParams(str string) PathParameters {
_paramsstr := strings.Split(str, ",")
if len(_paramsstr) == 0 {
return nil
}
params := make(PathParameters, 0) // PathParameters{}
// for i := 0; i < len(_paramsstr); i++ {
for i := range _paramsstr {
idxOfEq := strings.IndexRune(_paramsstr[i], '=')
if idxOfEq == -1 {
//error
return nil
}
key := _paramsstr[i][:idxOfEq]
val := _paramsstr[i][idxOfEq+1:]
params = append(params, PathParameter{key, val})
}
return params
}
// GetParamsLen returns the parameters length from a given path
func GetParamsLen(path string) uint8 {
var n uint
for i := 0; i < len(path); i++ {
if path[i] != ':' && path[i] != '*' { // ParameterStartByte & MatchEverythingByte
continue
}
n++
}
if n >= 255 {
return 255
}
return uint8(n)
}
// AddBranch adds a branch to the existing branch or to the tree if no branch has the prefix of
func (b *Branch) AddBranch(path string, middleware Middleware) {
fullPath := path
b.precedence++
numParams := GetParamsLen(path)
if len(b.part) > 0 || len(b.nodes) > 0 {
loop:
for {
if numParams > b.paramsLen {
b.paramsLen = numParams
}
i := 0
max := utils.FindLower(len(path), len(b.part))
for i < max && path[i] == b.part[i] {
i++
}
if i < len(b.part) {
node := Branch{
part: b.part[i:],
hasWildNode: b.hasWildNode,
tokens: b.tokens,
nodes: b.nodes,
middleware: b.middleware,
precedence: b.precedence - 1,
}
for i := range node.nodes {
if node.nodes[i].paramsLen > node.paramsLen {
node.paramsLen = node.nodes[i].paramsLen
}
}
b.nodes = []*Branch{&node}
b.tokens = string([]byte{b.part[i]})
b.part = path[:i]
b.middleware = nil
b.hasWildNode = false
}
if i < len(path) {
path = path[i:]
if b.hasWildNode {
b = b.nodes[0]
b.precedence++
if numParams > b.paramsLen {
b.paramsLen = numParams
}
numParams--
if len(path) >= len(b.part) && b.part == path[:len(b.part)] {
if len(b.part) >= len(path) || path[len(b.part)] == '/' {
continue loop
}
}
return
}
c := path[0]
if b.BranchCase == hasParams && c == '/' && len(b.nodes) == 1 {
b = b.nodes[0]
b.precedence++
continue loop
}
//we need the i here to be re-setting, so use the same i variable as we declare it on line 176
for i := range b.tokens {
if c == b.tokens[i] {
i = b.GivePrecedenceTo(i)
b = b.nodes[i]
continue loop
}
}
if c != ParameterStartByte && c != MatchEverythingByte {
b.tokens += string([]byte{c})
node := &Branch{
paramsLen: numParams,
}
b.nodes = append(b.nodes, node)
b.GivePrecedenceTo(len(b.tokens) - 1)
b = node
}
b.AddNode(numParams, path, fullPath, middleware)
return
} else if i == len(path) {
if b.middleware != nil {
return
}
b.middleware = middleware
}
return
}
} else {
b.AddNode(numParams, path, fullPath, middleware)
b.BranchCase = isRoot
}
}
// AddNode adds a branch as children to other Branch
func (b *Branch) AddNode(numParams uint8, path string, fullPath string, middleware Middleware) {
var offset int
for i, max := 0, len(path); numParams > 0; i++ {
c := path[i]
if c != ParameterStartByte && c != MatchEverythingByte {
continue
}
end := i + 1
for end < max && path[end] != '/' {
switch path[end] {
case ParameterStartByte, MatchEverythingByte:
default:
end++
}
}
if len(b.nodes) > 0 {
return
}
if end-i < 2 {
return
}
if c == ParameterStartByte {
if i > 0 {
b.part = path[offset:i]
offset = i
}
child := &Branch{
BranchCase: hasParams,
paramsLen: numParams,
}
b.nodes = []*Branch{child}
b.hasWildNode = true
b = child
b.precedence++
numParams--
if end < max {
b.part = path[offset:end]
offset = end
child := &Branch{
paramsLen: numParams,
precedence: 1,
}
b.nodes = []*Branch{child}
b = child
}
} else {
if end != max || numParams > 1 {
return
}
if len(b.part) > 0 && b.part[len(b.part)-1] == '/' {
return
}
i--
if path[i] != '/' {
return
}
b.part = path[offset:i]
child := &Branch{
hasWildNode: true,
BranchCase: matchEverything,
paramsLen: 1,
}
b.nodes = []*Branch{child}
b.tokens = string(path[i])
b = child
b.precedence++
child = &Branch{
part: path[i:],
BranchCase: matchEverything,
paramsLen: 1,
middleware: middleware,
precedence: 1,
}
b.nodes = []*Branch{child}
return
}
}
b.part = path[offset:]
b.middleware = middleware
}
// GetBranch is used by the Router, it finds and returns the correct branch for a path
func (b *Branch) GetBranch(path string, _params PathParameters) (middleware Middleware, params PathParameters, mustRedirect bool) {
params = _params
loop:
for {
if len(path) > len(b.part) {
if path[:len(b.part)] == b.part {
path = path[len(b.part):]
if !b.hasWildNode {
c := path[0]
for i := range b.tokens {
if c == b.tokens[i] {
b = b.nodes[i]
continue loop
}
}
mustRedirect = (path == Slash && b.middleware != nil)
return
}
b = b.nodes[0]
switch b.BranchCase {
case hasParams:
end := 0
for end < len(path) && path[end] != '/' {
end++
}
if cap(params) < int(b.paramsLen) {
params = make(PathParameters, 0, b.paramsLen)
}
i := len(params)
params = params[:i+1]
params[i].Key = b.part[1:]
params[i].Value = path[:end]
if end < len(path) {
if len(b.nodes) > 0 {
path = path[end:]
b = b.nodes[0]
continue loop
}
mustRedirect = (len(path) == end+1)
return
}
if middleware = b.middleware; middleware != nil {
return
} else if len(b.nodes) == 1 {
b = b.nodes[0]
mustRedirect = (b.part == Slash && b.middleware != nil)
}
return
case matchEverything:
if cap(params) < int(b.paramsLen) {
params = make(PathParameters, 0, b.paramsLen)
}
i := len(params)
params = params[:i+1]
params[i].Key = b.part[2:]
params[i].Value = path
middleware = b.middleware
return
default:
return
}
}
} else if path == b.part {
if middleware = b.middleware; middleware != nil {
return
}
if path == Slash && b.hasWildNode && b.BranchCase != isRoot {
mustRedirect = true
return
}
for i := range b.tokens {
if b.tokens[i] == '/' {
b = b.nodes[i]
mustRedirect = (len(b.part) == 1 && b.middleware != nil) ||
(b.BranchCase == matchEverything && b.nodes[0].middleware != nil)
return
}
}
return
}
mustRedirect = (path == Slash) ||
(len(b.part) == len(path)+1 && b.part[len(path)] == '/' &&
path == b.part[:len(b.part)-1] && b.middleware != nil)
return
}
}
// GivePrecedenceTo just adds the priority of this branch by an index
func (b *Branch) GivePrecedenceTo(index int) int {
b.nodes[index].precedence++
_precedence := b.nodes[index].precedence
newindex := index
for newindex > 0 && b.nodes[newindex-1].precedence < _precedence {
tmpN := b.nodes[newindex-1]
b.nodes[newindex-1] = b.nodes[newindex]
b.nodes[newindex] = tmpN
newindex--
}
if newindex != index {
b.tokens = b.tokens[:newindex] +
b.tokens[index:index+1] +
b.tokens[newindex:index] + b.tokens[index+1:]
}
return newindex
}

37
config/basicauth.go Normal file
View File

@ -0,0 +1,37 @@
package config
import (
"time"
"github.com/imdario/mergo"
)
const (
DefaultBasicAuthRealm = "Authorization Required"
DefaultBasicAuthContextKey = "auth"
)
type BasicAuth 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 'auth'
ContextKey string
// Expires expiration duration, default is 0 never expires
Expires time.Duration
}
// DefaultBasicAuth returns the default configs for the BasicAuth middleware
func DefaultBasicAuth() BasicAuth {
return BasicAuth{make(map[string]string), DefaultBasicAuthRealm, DefaultBasicAuthContextKey, 0}
}
// Merge MergeSingle the default with the given config and returns the result
func (c BasicAuth) MergeSingle(cfg BasicAuth) (config BasicAuth) {
config = cfg
mergo.Merge(&config, c)
return
}

16
config/config.go Normal file
View File

@ -0,0 +1,16 @@
// Package config defines the default settings and semantic variables
package config
import (
"time"
)
var (
// StaticCacheDuration expiration duration for INACTIVE file handlers
StaticCacheDuration = 20 * time.Second
// CompressedFileSuffix is the suffix to add to the name of
// cached compressed file when using the .StaticFS function.
//
// Defaults to iris-fasthttp.gz
CompressedFileSuffix = "iris-fasthttp.gz"
)

44
config/editor.go Normal file
View File

@ -0,0 +1,44 @@
package config
import "github.com/imdario/mergo"
type Editor struct {
// Host if empty used the iris server's host
Host string
// Port if 0 4444
Port int
// WorkingDir if empty "./"
WorkingDir string
// Username if empty iris
Username string
// Password if empty admin!123
Password string
}
// DefaultEditor returns the default configs for the Editor plugin
func DefaultEditor() Editor {
return Editor{"", 4444, "." + pathSeparator, DefaultUsername, DefaultPassword}
}
// Merge merges the default with the given config and returns the result
func (c Editor) Merge(cfg []Editor) (config Editor) {
if len(cfg) > 0 {
config = cfg[0]
mergo.Merge(&config, c)
} else {
_default := c
config = _default
}
return
}
// Merge MergeSingle the default with the given config and returns the result
func (c Editor) MergeSingle(cfg Editor) (config Editor) {
config = cfg
mergo.Merge(&config, c)
return
}

166
config/iris.go Normal file
View File

@ -0,0 +1,166 @@
package config
import (
"github.com/imdario/mergo"
)
const DefaultProfilePath = "/debug/pprof"
type (
// Iris configs for the station
// All fields can be changed before server's listen except the DisablePathCorrection field
//
// MaxRequestBodySize is the only options that can be changed after server listen -
// using Config().MaxRequestBodySize = ...
// Render's rest config can be changed after declaration but before server's listen -
// using Config().Render.Rest...
// Render's Template config can be changed after declaration but before server's listen -
// using Config().Render.Template...
// Sessions config can be changed after declaration but before server's listen -
// using Config().Sessions...
// and so on...
Iris struct {
// MaxRequestBodySize Maximum request body size.
//
// The server rejects requests with bodies exceeding this limit.
//
// By default request body size is -1, unlimited.
MaxRequestBodySize int64
// DisablePathCorrection corrects and redirects the requested path to the registed path
// for example, if /home/ path is requested but no handler for this Route found,
// then the Router checks if /home handler exists, if yes,
// (permant)redirects the client to the correct path /home
//
// Default is false
DisablePathCorrection bool
// DisablePathEscape when is false then its escapes the path, the named parameters (if any).
// Change to true it if you want something like this https://github.com/kataras/iris/issues/135 to work
//
// When do you need to Disable(true) it:
// accepts parameters with slash '/'
// Request: http://localhost:8080/details/Project%2FDelta
// ctx.Param("project") returns the raw named parameter: Project%2FDelta
// which you can escape it manually with net/url:
// projectName, _ := url.QueryUnescape(c.Param("project").
// Look here: https://github.com/kataras/iris/issues/135 for more
//
// Default is false
DisablePathEscape bool
// DisableLog turn it to true if you want to disable logger,
// Iris prints/logs ONLY errors, so be careful when you enable it
DisableLog bool
// DisableBanner outputs the iris banner at startup
//
// Default is false
DisableBanner bool
// Profile set to true to enable web pprof (debug profiling)
// Default is false, enabling makes available these 7 routes:
// /debug/pprof/cmdline
// /debug/pprof/profile
// /debug/pprof/symbol
// /debug/pprof/goroutine
// /debug/pprof/heap
// /debug/pprof/threadcreate
// /debug/pprof/pprof/block
Profile bool
// ProfilePath change it if you want other url path than the default
// Default is /debug/pprof , which means yourhost.com/debug/pprof
ProfilePath string
// Sessions the config for sessions
// contains 3(three) properties
// Provider: (look /sessions/providers)
// Secret: cookie's name (string)
// Life: cookie life (time.Duration)
Sessions Sessions
// Render contains the configs for template and rest configuration
Render Render
Websocket Websocket
}
// Render struct keeps organise all configuration about rendering, templates and rest currently.
Render struct {
// Template the configs for template
Template Template
// Rest configs for rendering.
//
// these options inside this config don't have any relation with the TemplateEngine
// from github.com/kataras/iris/rest
Rest Rest
}
)
// DefaultRender returns default configuration for templates and rest rendering
func DefaultRender() Render {
return Render{
// set the default template config both not nil and default Engine to Standar
Template: DefaultTemplate(),
// set the default configs for rest
Rest: DefaultRest(),
}
}
// Default returns the default configuration for the Iris staton
func Default() Iris {
return Iris{
DisablePathCorrection: false,
DisablePathEscape: false,
MaxRequestBodySize: -1,
DisableLog: false,
DisableBanner: false,
Profile: false,
ProfilePath: DefaultProfilePath,
Sessions: DefaultSessions(),
Render: DefaultRender(),
Websocket: DefaultWebsocket(),
}
}
// Merge merges the default with the given config and returns the result
// receives an array because the func caller is variadic
func (c Iris) Merge(cfg []Iris) (config Iris) {
// I tried to make it more generic with interfaces for all configs, inside config.go but it fails,
// so do it foreach configuration np they aint so much...
if cfg != nil && len(cfg) > 0 {
config = cfg[0]
mergo.Merge(&config, c)
} else {
_default := c
config = _default
}
return
}
// Merge MergeSingle the default with the given config and returns the result
func (c Iris) MergeSingle(cfg Iris) (config Iris) {
config = cfg
mergo.Merge(&config, c)
return
}
/* maybe some day
// FromFile returns the configuration for Iris station
//
// receives one parameter
// pathIni(string) the file path of the configuration-ini style
//
// returns an error if something bad happens
func FromFile(pathIni string) (c Iris, err error) {
c = Iris{}
err = ini.MapTo(&c, pathIni)
return
}
*/

40
config/iriscontrol.go Normal file
View File

@ -0,0 +1,40 @@
package config
import "github.com/imdario/mergo"
var (
// DefaultUsername used for default (basic auth) username in IrisControl's & Editor's default configuration
DefaultUsername = "iris"
// DefaultPassword used for default (basic auth) password in IrisControl's & Editor's default configuration
DefaultPassword = "admin!123"
)
// IrisControl the options which iris control needs
// contains the port (int) and authenticated users with their passwords (map[string]string)
type IrisControl struct {
// Port the port
Port int
// Users the authenticated users, [username]password
Users map[string]string
}
// DefaultIrisControl returns the default configs for IrisControl plugin
func DefaultIrisControl() IrisControl {
users := make(map[string]string, 0)
users[DefaultUsername] = DefaultPassword
return IrisControl{4000, users}
}
// Merge merges the default with the given config and returns the result
func (c IrisControl) Merge(cfg []IrisControl) (config IrisControl) {
if len(cfg) > 0 {
config = cfg[0]
mergo.Merge(&config, c)
} else {
_default := c
config = _default
}
return
}

48
config/logger.go Normal file
View File

@ -0,0 +1,48 @@
package config
import "github.com/imdario/mergo"
import (
"io"
"os"
)
var (
// TimeFormat default time format for any kind of datetime parsing
TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
)
type (
Logger struct {
Out io.Writer
Prefix string
Flag int
}
)
func DefaultLogger() Logger {
return Logger{Out: os.Stdout, Prefix: "", Flag: 0}
}
// Merge merges the default with the given config and returns the result
func (c Logger) Merge(cfg []Logger) (config Logger) {
if len(cfg) > 0 {
config = cfg[0]
mergo.Merge(&config, c)
} else {
_default := c
config = _default
}
return
}
// Merge MergeSingle the default with the given config and returns the result
func (c Logger) MergeSingle(cfg Logger) (config Logger) {
config = cfg
mergo.Merge(&config, c)
return
}

189
config/render.go Normal file
View File

@ -0,0 +1,189 @@
package config
import (
"html/template"
"github.com/flosch/pongo2"
"github.com/imdario/mergo"
)
const (
NoEngine EngineType = -1
HTMLEngine EngineType = 0
PongoEngine EngineType = 1
MarkdownEngine EngineType = 2
JadeEngine EngineType = 3
AmberEngine EngineType = 4
DefaultEngine EngineType = HTMLEngine
// to disable layout for a particular file
NoLayout = "@.|.@iris_no_layout@.|.@"
)
var (
// Charset character encoding.
Charset = "UTF-8"
)
type (
// Rest is a struct for specifying configuration options for the rest.Render object.
Rest struct {
// Appends the given character set to the Content-Type header. Default is "UTF-8".
Charset string
// Gzip enable it if you want to render with gzip compression. Default is false
Gzip bool
// Outputs human readable JSON.
IndentJSON bool
// Outputs human readable XML. Default is false.
IndentXML bool
// Prefixes the JSON output with the given bytes. Default is false.
PrefixJSON []byte
// Prefixes the XML output with the given bytes.
PrefixXML []byte
// Unescape HTML characters "&<>" to their original values. Default is false.
UnEscapeHTML bool
// Streams JSON responses instead of marshalling prior to sending. Default is false.
StreamingJSON bool
// Disables automatic rendering of http.StatusInternalServerError when an error occurs. Default is false.
DisableHTTPErrorRendering bool
// MarkdownSanitize sanitizes the markdown. Default is false.
MarkdownSanitize bool
}
EngineType int8
Template struct {
// contains common configs for both HTMLTemplate & Pongo
Engine EngineType
Gzip bool
// Minify minifies the html result,
// Note: according to this https://github.com/tdewolff/minify/issues/35, also it removes some </tags> when minify on writer, remove this from Iris until fix.
// Default is false
//Minify bool
IsDevelopment bool
Directory string
Extensions []string
ContentType string
Charset string
Asset func(name string) ([]byte, error)
AssetNames func() []string
Layout string
HTMLTemplate HTMLTemplate // contains specific configs for HTMLTemplate standard html/template
Pongo Pongo // contains specific configs for pongo2
// Markdown template engine it doesn't supports Layout & binding context
Markdown Markdown // contains specific configs for markdown
Jade Jade // contains specific configs for Jade
Amber Amber // contains specific configs for Amber
}
HTMLTemplate struct {
RequirePartials bool
// Delims
Left string
Right string
// Funcs for HTMLTemplate html/template
Funcs template.FuncMap
}
Pongo struct {
// Filters for pongo2, map[name of the filter] the filter function . The filters are auto register
Filters map[string]pongo2.FilterFunction
// Globals share context fields between templates. https://github.com/flosch/pongo2/issues/35
Globals map[string]interface{}
}
Markdown struct {
Sanitize bool // if true then returns safe html, default is false
}
// Jade empty for now
// stay tuned
Jade struct {
}
Amber struct {
// Funcs for the html/template result, amber default funcs are not overrided so use it without worries
Funcs template.FuncMap
}
)
// DefaultRest returns the default config for rest
func DefaultRest() Rest {
return Rest{
Charset: Charset,
IndentJSON: false,
IndentXML: false,
PrefixJSON: []byte(""),
PrefixXML: []byte(""),
UnEscapeHTML: false,
StreamingJSON: false,
DisableHTTPErrorRendering: false,
MarkdownSanitize: false,
}
}
// Merge merges the default with the given config and returns the result
func (c Rest) Merge(cfg []Rest) (config Rest) {
if len(cfg) > 0 {
config = cfg[0]
mergo.Merge(&config, c)
} else {
_default := c
config = _default
}
return
}
// MergeSingle merges the default with the given config and returns the result
func (c Rest) MergeSingle(cfg Rest) (config Rest) {
config = cfg
mergo.Merge(&config, c)
return
}
func DefaultTemplate() Template {
return Template{
Engine: DefaultEngine, //or HTMLTemplate
Gzip: false,
IsDevelopment: false,
Directory: "templates",
Extensions: []string{".html"},
ContentType: "text/html",
Charset: "UTF-8",
Layout: "", // currently this is the only config which not working for pongo2 yet but I will find a way
HTMLTemplate: HTMLTemplate{Left: "{{", Right: "}}", Funcs: template.FuncMap{}},
Pongo: Pongo{Filters: make(map[string]pongo2.FilterFunction, 0), Globals: make(map[string]interface{}, 0)},
Markdown: Markdown{Sanitize: false},
Amber: Amber{Funcs: template.FuncMap{}},
Jade: Jade{},
}
}
// Merge merges the default with the given config and returns the result
func (c Template) Merge(cfg []Template) (config Template) {
if len(cfg) > 0 {
config = cfg[0]
mergo.Merge(&config, c)
} else {
_default := c
config = _default
}
return
}
// MergeSingle merges the default with the given config and returns the result
func (c Template) MergeSingle(cfg Template) (config Template) {
config = cfg
mergo.Merge(&config, c)
return
}

44
config/server.go Normal file
View File

@ -0,0 +1,44 @@
package config
import (
"os"
"github.com/imdario/mergo"
)
const (
// DefaultServerAddr the default server addr
DefaultServerAddr = ":8080"
)
// ServerName the response header of the 'Server' value when writes to the client
const ServerName = "iris"
// Server used inside server for listening
type Server struct {
// ListenningAddr the addr that server listens to
ListeningAddr string
CertFile string
KeyFile string
// Mode this is for unix only
Mode os.FileMode
}
// DefaultServer returns the default configs for the server
func DefaultServer() Server {
return Server{DefaultServerAddr, "", "", 0}
}
// Merge merges the default with the given config and returns the result
func (c Server) Merge(cfg []Server) (config Server) {
if len(cfg) > 0 {
config = cfg[0]
mergo.Merge(&config, c)
} else {
_default := c
config = _default
}
return
}

145
config/sessions.go Normal file
View File

@ -0,0 +1,145 @@
package config
import (
"time"
"github.com/imdario/mergo"
)
var (
universe time.Time // 0001-01-01 00:00:00 +0000 UTC
// CookieExpireNever the default cookie's life for sessions, unlimited
CookieExpireNever = universe
)
const (
// DefaultCookieName the secret cookie's name for sessions
DefaultCookieName = "irissessionid"
DefaultSessionGcDuration = time.Duration(2) * time.Hour
// DefaultRedisNetwork the redis network option, "tcp"
DefaultRedisNetwork = "tcp"
// DefaultRedisAddr the redis address option, "127.0.0.1:6379"
DefaultRedisAddr = "127.0.0.1:6379"
// DefaultRedisIdleTimeout the redis idle timeout option, time.Duration(5) * time.Minute
DefaultRedisIdleTimeout = time.Duration(5) * time.Minute
// DefaultRedisMaxAgeSeconds the redis storage last parameter (SETEX), 31556926.0 (1 year)
DefaultRedisMaxAgeSeconds = 31556926.0 //1 year
)
type (
// Redis the redis configuration used inside sessions
Redis struct {
// Network "tcp"
Network string
// Addr "127.0.01:6379"
Addr string
// Password string .If no password then no 'AUTH'. Default ""
Password string
// If Database is empty "" then no 'SELECT'. Default ""
Database string
// MaxIdle 0 no limit
MaxIdle int
// MaxActive 0 no limit
MaxActive int
// IdleTimeout time.Duration(5) * time.Minute
IdleTimeout time.Duration
// Prefix "myprefix-for-this-website". Default ""
Prefix string
// MaxAgeSeconds how much long the redis should keep the session in seconds. Default 31556926.0 (1 year)
MaxAgeSeconds int
}
// Sessions the configuration for sessions
// has 4 fields
// first is the providerName (string) ["memory","redis"]
// second is the cookieName, the session's name (string) ["mysessionsecretcookieid"]
// third is the time which the client's cookie expires
// forth is the gcDuration (time.Duration) when this time passes it removes the unused sessions from the memory until the user come back
Sessions struct {
// Provider string, usage iris.Config().Provider = "memory" or "redis". If you wan to customize redis then import the package, and change it's config
Provider string
// Cookie string, the session's client cookie name, for example: "irissessionid"
Cookie string
//Expires the date which the cookie must expires. Default infinitive/unlimited life
Expires time.Time
// GcDuration every how much duration(GcDuration) the memory should be clear for unused cookies (GcDuration)
// for example: time.Duration(2)*time.Hour. it will check every 2 hours if cookie hasn't be used for 2 hours,
// deletes it from memory until the user comes back, then the session continue to work as it was
//
// Default 2 hours
GcDuration time.Duration
}
)
// DefaultSessions the default configs for Sessions
func DefaultSessions() Sessions {
return Sessions{
Provider: "memory", // the default provider is "memory", if you set it to "" means that sessions are disabled.
Cookie: DefaultCookieName,
Expires: CookieExpireNever,
GcDuration: DefaultSessionGcDuration,
}
}
// Merge merges the default with the given config and returns the result
func (c Sessions) Merge(cfg []Sessions) (config Sessions) {
if len(cfg) > 0 {
config = cfg[0]
mergo.Merge(&config, c)
} else {
_default := c
config = _default
}
return
}
// Merge MergeSingle the default with the given config and returns the result
func (c Sessions) MergeSingle(cfg Sessions) (config Sessions) {
config = cfg
mergo.Merge(&config, c)
return
}
// DefaultRedis returns the default configuration for Redis service
func DefaultRedis() Redis {
return Redis{
Network: DefaultRedisNetwork,
Addr: DefaultRedisAddr,
Password: "",
Database: "",
MaxIdle: 0,
MaxActive: 0,
IdleTimeout: DefaultRedisIdleTimeout,
Prefix: "",
MaxAgeSeconds: DefaultRedisMaxAgeSeconds,
}
}
// Merge merges the default with the given config and returns the result
func (c Redis) Merge(cfg []Redis) (config Redis) {
if len(cfg) > 0 {
config = cfg[0]
mergo.Merge(&config, c)
} else {
_default := c
config = _default
}
return
}
// Merge MergeSingle the default with the given config and returns the result
func (c Redis) MergeSingle(cfg Redis) (config Redis) {
config = cfg
mergo.Merge(&config, c)
return
}

132
config/typescript.go Normal file
View File

@ -0,0 +1,132 @@
package config
import (
"os"
"reflect"
"github.com/imdario/mergo"
)
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"`
}
Typescript struct {
Bin string
Dir string
Ignore string
Tsconfig Tsconfig
Editor Editor
}
)
// 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
func (tsconfig Tsconfig) CompilerArgs() []string {
//val := reflect.ValueOf(tsconfig).Elem().FieldByName("CompilerOptions") -> for tsconfig *Tsconfig
val := reflect.ValueOf(tsconfig).FieldByName("CompilerOptions")
compilerOpts := make([]string, val.NumField())
for i := 0; i < val.NumField(); i++ {
typeField := val.Type().Field(i)
compilerOpts[i] = "--" + typeField.Tag.Get("json")
}
return compilerOpts
}
// 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: "es5",
NoImplicitAny: false,
SourceMap: false,
},
Exclude: []string{"node_modules"},
}
}
// DefaultTypescript returns the default Options of the Typescript plugin
// Bin and Editor are setting in runtime via the plugin
func DefaultTypescript() Typescript {
root, err := os.Getwd()
if err != nil {
panic("Typescript Plugin: Cannot get the Current Working Directory !!! [os.getwd()]")
}
c := Typescript{Dir: root + pathSeparator, Ignore: nodeModules, Tsconfig: DefaultTsconfig()}
return c
}
// Merge merges the default with the given config and returns the result
func (c Typescript) Merge(cfg []Typescript) (config Typescript) {
if len(cfg) > 0 {
config = cfg[0]
mergo.Merge(&config, c)
} else {
_default := c
config = _default
}
return
}

78
config/websocket.go Normal file
View File

@ -0,0 +1,78 @@
package config
import (
"time"
"github.com/imdario/mergo"
)
// Currently only these 5 values are used for real
const (
// DefaultWriteTimeout 10 * time.Second
DefaultWriteTimeout = 10 * time.Second
// DefaultPongTimeout 60 * time.Second
DefaultPongTimeout = 60 * time.Second
// DefaultPingPeriod (DefaultPongTimeout * 9) / 10
DefaultPingPeriod = (DefaultPongTimeout * 9) / 10
// DefaultMaxMessageSize 1024
DefaultMaxMessageSize = 1024
)
//
// Websocket the config contains options for 'websocket' package
type Websocket struct {
// WriteTimeout time allowed to write a message to the connection.
// Default value is 10 * 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 int
// 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
// Headers the response headers before upgrader
// Default is empty
Headers map[string]string
}
// DefaultWebsocket returns the default config for iris-ws websocket package
func DefaultWebsocket() Websocket {
return Websocket{
WriteTimeout: DefaultWriteTimeout,
PongTimeout: DefaultPongTimeout,
PingPeriod: DefaultPingPeriod,
MaxMessageSize: DefaultMaxMessageSize,
Headers: make(map[string]string, 0),
Endpoint: "",
}
}
// Merge merges the default with the given config and returns the result
func (c Websocket) Merge(cfg []Websocket) (config Websocket) {
if len(cfg) > 0 {
config = cfg[0]
mergo.Merge(&config, c)
} else {
_default := c
config = _default
}
return
}
// MergeSingle merges the default with the given config and returns the result
func (c Websocket) MergeSingle(cfg Websocket) (config Websocket) {
config = cfg
mergo.Merge(&config, c)
return
}

162
context.go Normal file
View File

@ -0,0 +1,162 @@
/*
Context.go Implements: ./context/context.go ,
files: context_renderer.go, context_storage.go, context_request.go, context_response.go
*/
package iris
import (
"reflect"
"runtime"
"time"
"github.com/kataras/iris/context"
"github.com/kataras/iris/sessions/store"
"github.com/valyala/fasthttp"
)
const (
// DefaultUserAgent default to 'iris' but it is not used anywhere yet
DefaultUserAgent = "iris"
// ContentType represents the header["Content-Type"]
ContentType = "Content-Type"
// ContentLength represents the header["Content-Length"]
ContentLength = "Content-Length"
// ContentHTML is the string of text/html response headers
ContentHTML = "text/html"
// ContentBINARY is the string of application/octet-stream response headers
ContentBINARY = "application/octet-stream"
// LastModified "Last-Modified"
LastModified = "Last-Modified"
// IfModifiedSince "If-Modified-Since"
IfModifiedSince = "If-Modified-Since"
// ContentDisposition "Content-Disposition"
ContentDisposition = "Content-Disposition"
// TimeFormat default time format for any kind of datetime parsing
TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
// stopExecutionPosition used inside the Context, is the number which shows us that the context's middleware manualy stop the execution
stopExecutionPosition = 255
)
type (
Map map[string]interface{}
// Context is resetting every time a request is coming to the server
// it is not good practice to use this object in goroutines, for these cases use the .Clone()
Context struct {
*fasthttp.RequestCtx
Params PathParameters
station *Iris
//keep track all registed middleware (handlers)
middleware Middleware
sessionStore store.IStore
// pos is the position number of the Context, look .Next to understand
pos uint8
}
)
var _ context.IContext = &Context{}
// GetRequestCtx returns the current fasthttp context
func (ctx *Context) GetRequestCtx() *fasthttp.RequestCtx {
return ctx.RequestCtx
}
// Implement the golang.org/x/net/context , as requested by the community, which is used inside app engine
// also this will give me the ability to use appengine's memcache with this context, if this needed.
// Deadline returns the time when this Context will be canceled, if any.
func (ctx *Context) Deadline() (deadline time.Time, ok bool) {
return
}
// Done returns a channel that is closed when this Context is canceled
// or times out.
func (ctx *Context) Done() <-chan struct{} {
return nil
}
// Err indicates why this context was canceled, after the Done channel
// is closed.
func (ctx *Context) Err() error {
return nil
}
// Value returns the value associated with key or nil if none.
func (ctx *Context) Value(key interface{}) interface{} {
if key == 0 {
return ctx.Request
}
if keyAsString, ok := key.(string); ok {
val := ctx.GetString(keyAsString)
return val
}
return nil
}
// Reset resets the Context with a given domain.Response and domain.Request
// the context is ready-to-use after that, just like a new Context
// I use it for zero rellocation memory
func (ctx *Context) Reset(reqCtx *fasthttp.RequestCtx) {
ctx.Params = ctx.Params[0:0]
ctx.sessionStore = nil
ctx.middleware = nil
ctx.RequestCtx = reqCtx
}
// Clone use that method if you want to use the context inside a goroutine
func (ctx *Context) Clone() context.IContext {
var cloneContext = *ctx
cloneContext.pos = 0
//copy params
p := ctx.Params
cpP := make(PathParameters, len(p))
copy(cpP, p)
cloneContext.Params = cpP
//copy middleware
m := ctx.middleware
cpM := make(Middleware, len(m))
copy(cpM, m)
cloneContext.middleware = cpM
// we don't copy the sessionStore for more than one reasons...
return &cloneContext
}
// Do calls the first handler only, it's like Next with negative pos, used only on Router&MemoryRouter
func (ctx *Context) Do() {
ctx.pos = 0
ctx.middleware[0].Serve(ctx)
}
// Next calls all the next handler from the middleware stack, it used inside a middleware
func (ctx *Context) Next() {
//set position to the next
ctx.pos++
midLen := uint8(len(ctx.middleware))
//run the next
if ctx.pos < midLen {
ctx.middleware[ctx.pos].Serve(ctx)
}
}
// StopExecution just sets the .pos to 255 in order to not move to the next middlewares(if any)
func (ctx *Context) StopExecution() {
ctx.pos = stopExecutionPosition
}
//
// IsStopped checks and returns true if the current position of the Context is 255, means that the StopExecution has called
func (ctx *Context) IsStopped() bool {
return ctx.pos == stopExecutionPosition
}
// GetHandlerName as requested returns the stack-name of the function which the Middleware is setted from
func (ctx *Context) GetHandlerName() string {
return runtime.FuncForPC(reflect.ValueOf(ctx.middleware[len(ctx.middleware)-1]).Pointer()).Name()
}

118
context/context.go Normal file
View File

@ -0,0 +1,118 @@
package context
import (
"bufio"
"html/template"
"io"
"time"
"github.com/kataras/iris/sessions/store"
"github.com/valyala/fasthttp"
"golang.org/x/net/context"
)
type (
// IContext the interface for the Context
IContext interface {
context.Context
IContextRenderer
IContextStorage
IContextBinder
IContextRequest
IContextResponse
Reset(*fasthttp.RequestCtx)
GetRequestCtx() *fasthttp.RequestCtx
Clone() IContext
Do()
Next()
StopExecution()
IsStopped() bool
GetHandlerName() string
}
// IContextBinder is part of the IContext
IContextBinder interface {
ReadJSON(interface{}) error
ReadXML(interface{}) error
ReadForm(formObject interface{}) error
}
// IContextRenderer is part of the IContext
IContextRenderer interface {
Write(string, ...interface{})
WriteHTML(int, string)
// Data writes out the raw bytes as binary data.
Data(status int, v []byte) error
// HTML builds up the response from the specified template and bindings.
HTML(status int, name string, binding interface{}, layout ...string) error
// Render same as .HTML but with status to iris.StatusOK (200)
Render(name string, binding interface{}, layout ...string) error
// JSON marshals the given interface object and writes the JSON response.
JSON(status int, v interface{}) error
// JSONP marshals the given interface object and writes the JSON response.
JSONP(status int, callback string, v interface{}) error
// Text writes out a string as plain text.
Text(status int, v string) error
// XML marshals the given interface object and writes the XML response.
XML(status int, v interface{}) error
ExecuteTemplate(*template.Template, interface{}) error
ServeContent(io.ReadSeeker, string, time.Time, bool) error
ServeFile(string, bool) error
SendFile(filename string, destinationName string) error
Stream(func(*bufio.Writer))
StreamWriter(cb func(writer *bufio.Writer))
StreamReader(io.Reader, int)
}
// IContextRequest is part of the IContext
IContextRequest interface {
Param(string) string
ParamInt(string) (int, error)
URLParam(string) string
URLParamInt(string) (int, error)
URLParams() map[string]string
MethodString() string
HostString() string
PathString() string
RequestIP() string
RemoteAddr() string
RequestHeader(k string) string
PostFormValue(string) string
}
// IContextResponse is part of the IContext
IContextResponse interface {
// SetStatusCode sets the http status code
SetStatusCode(int)
// SetContentType sets the "Content-Type" header, receives the value
SetContentType(string)
// SetHeader sets the response headers first parameter is the key, second is the value
SetHeader(string, string)
Redirect(string, ...int)
// Errors
NotFound()
Panic()
EmitError(int)
//
}
// IContextStorage is part of the IContext
IContextStorage interface {
Get(string) interface{}
GetString(string) string
GetInt(string) int
Set(string, interface{})
SetCookie(*fasthttp.Cookie)
SetCookieKV(string, string)
RemoveCookie(string)
// Flash messages
GetFlash(string) string
GetFlashBytes(string) ([]byte, error)
SetFlash(string, string)
SetFlashBytes(string, []byte)
Session() store.IStore
SessionDestroy()
}
)

196
context_renderer.go Normal file
View File

@ -0,0 +1,196 @@
package iris
import (
"bufio"
"fmt"
"html/template"
"io"
"os"
"path"
"time"
"github.com/kataras/iris/utils"
"github.com/klauspost/compress/gzip"
)
// Write writes a string via the context's ResponseWriter
func (ctx *Context) Write(format string, a ...interface{}) {
//this doesn't work with gzip, so just write the []byte better |ctx.ResponseWriter.WriteString(fmt.Sprintf(format, a...))
ctx.RequestCtx.WriteString(fmt.Sprintf(format, a...))
}
// WriteHTML writes html string with a http status
func (ctx *Context) WriteHTML(httpStatus int, htmlContents string) {
ctx.SetContentType(ContentHTML + ctx.station.rest.CompiledCharset)
ctx.RequestCtx.SetStatusCode(httpStatus)
ctx.RequestCtx.WriteString(htmlContents)
}
// Data writes out the raw bytes as binary data.
func (ctx *Context) Data(status int, v []byte) error {
return ctx.station.rest.Data(ctx.RequestCtx, status, v)
}
// HTML builds up the response from the specified template and bindings.
// Note: parameter layout has meaning only when using the iris.HTMLTemplate
func (ctx *Context) HTML(status int, name string, binding interface{}, layout ...string) error {
ctx.SetStatusCode(status)
return ctx.station.templates.Render(ctx, name, binding, layout...)
}
// Render same as .HTML but with status to iris.StatusOK (200)
func (ctx *Context) Render(name string, binding interface{}, layout ...string) error {
return ctx.HTML(StatusOK, name, binding, layout...)
}
// Render accepts a template filename, its context data and returns the result of the parsed template (string)
func (ctx *Context) RenderString(name string, binding interface{}, layout ...string) (result string, err error) {
return ctx.station.templates.RenderString(name, binding, layout...)
}
// JSON marshals the given interface object and writes the JSON response.
func (ctx *Context) JSON(status int, v interface{}) error {
return ctx.station.rest.JSON(ctx.RequestCtx, status, v)
}
// JSONP marshals the given interface object and writes the JSON response.
func (ctx *Context) JSONP(status int, callback string, v interface{}) error {
return ctx.station.rest.JSONP(ctx.RequestCtx, status, callback, v)
}
// Text writes out a string as plain text.
func (ctx *Context) Text(status int, v string) error {
return ctx.station.rest.Text(ctx.RequestCtx, status, v)
}
// XML marshals the given interface object and writes the XML response.
func (ctx *Context) XML(status int, v interface{}) error {
return ctx.station.rest.XML(ctx.RequestCtx, status, v)
}
// MarkdownString parses the (dynamic) markdown string and returns the converted html string
func (ctx *Context) MarkdownString(markdown string) string {
return ctx.station.rest.Markdown([]byte(markdown))
}
// Markdown parses and renders to the client a particular (dynamic) markdown string
// accepts two parameters
// first is the http status code
// second is the markdown string
func (ctx *Context) Markdown(status int, markdown string) {
ctx.WriteHTML(status, ctx.MarkdownString(markdown))
}
// ExecuteTemplate executes a simple html template, you can use that if you already have the cached templates
// the recommended way to render is to use iris.Templates("./templates/path/*.html") and ctx.RenderFile("filename.html",struct{})
// accepts 2 parameters
// the first parameter is the template (*template.Template)
// the second parameter is the page context (interfac{})
// returns an error if any errors occurs while executing this template
func (ctx *Context) ExecuteTemplate(tmpl *template.Template, pageContext interface{}) error {
ctx.RequestCtx.SetContentType(ContentHTML + ctx.station.rest.CompiledCharset)
return ErrTemplateExecute.With(tmpl.Execute(ctx.RequestCtx.Response.BodyWriter(), pageContext))
}
// ServeContent serves content, headers are autoset
// receives three parameters, it's low-level function, instead you can use .ServeFile(string)
//
// You can define your own "Content-Type" header also, after this function call
func (ctx *Context) ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) (err error) {
if t, err := time.Parse(TimeFormat, ctx.RequestHeader(IfModifiedSince)); err == nil && modtime.Before(t.Add(1*time.Second)) {
ctx.RequestCtx.Response.Header.Del(ContentType)
ctx.RequestCtx.Response.Header.Del(ContentLength)
ctx.RequestCtx.SetStatusCode(StatusNotModified)
return nil
}
ctx.RequestCtx.Response.Header.Set(ContentType, utils.TypeByExtension(filename))
ctx.RequestCtx.Response.Header.Set(LastModified, modtime.UTC().Format(TimeFormat))
ctx.RequestCtx.SetStatusCode(StatusOK)
var out io.Writer
if gzipCompression {
ctx.RequestCtx.Response.Header.Add("Content-Encoding", "gzip")
gzipWriter := ctx.station.gzipWriterPool.Get().(*gzip.Writer)
gzipWriter.Reset(ctx.RequestCtx.Response.BodyWriter())
defer gzipWriter.Close()
defer ctx.station.gzipWriterPool.Put(gzipWriter)
out = gzipWriter
} else {
out = ctx.RequestCtx.Response.BodyWriter()
}
_, err = io.Copy(out, content)
return ErrServeContent.With(err)
}
// ServeFile serves a view file, to send a file ( zip for example) to the client you should use the SendFile(serverfilename,clientfilename)
// receives two parameters
// filename/path (string)
// gzipCompression (bool)
//
// You can define your own "Content-Type" header also, after this function call
func (ctx *Context) ServeFile(filename string, gzipCompression bool) error {
f, err := os.Open(filename)
if err != nil {
return fmt.Errorf("%d", 404)
}
defer f.Close()
fi, _ := f.Stat()
if fi.IsDir() {
filename = path.Join(filename, "index.html")
f, err = os.Open(filename)
if err != nil {
return fmt.Errorf("%d", 404)
}
fi, _ = f.Stat()
}
return ctx.ServeContent(f, fi.Name(), fi.ModTime(), gzipCompression)
}
// SendFile sends file for force-download to the client
//
// You can define your own "Content-Type" header also, after this function call
// for example: ctx.Response.Header.Set("Content-Type","thecontent/type")
func (ctx *Context) SendFile(filename string, destinationName string) error {
err := ctx.ServeFile(filename, false)
if err != nil {
return err
}
ctx.RequestCtx.Response.Header.Set(ContentDisposition, "attachment;filename="+destinationName)
return nil
}
// Stream same as StreamWriter
func (ctx *Context) Stream(cb func(writer *bufio.Writer)) {
ctx.StreamWriter(cb)
}
// StreamWriter registers the given stream writer for populating
// response body.
//
//
// This function may be used in the following cases:
//
// * if response body is too big (more than 10MB).
// * if response body is streamed from slow external sources.
// * if response body must be streamed to the client in chunks.
// (aka `http server push`).
func (ctx *Context) StreamWriter(cb func(writer *bufio.Writer)) {
ctx.RequestCtx.SetBodyStreamWriter(cb)
}
// StreamReader sets response body stream and, optionally body size.
//
// If bodySize is >= 0, then the bodyStream must provide exactly bodySize bytes
// before returning io.EOF.
//
// If bodySize < 0, then bodyStream is read until io.EOF.
//
// bodyStream.Close() is called after finishing reading all body data
// if it implements io.Closer.
//
// See also StreamReader.
func (ctx *Context) StreamReader(bodyStream io.Reader, bodySize int) {
ctx.RequestCtx.Response.SetBodyStream(bodyStream, bodySize)
}

129
context_request.go Normal file
View File

@ -0,0 +1,129 @@
package iris
import (
"net"
"strconv"
"strings"
"github.com/kataras/iris/bindings"
"github.com/kataras/iris/utils"
"github.com/valyala/fasthttp"
)
// Param returns the string representation of the key's path named parameter's value
func (ctx *Context) Param(key string) string {
return ctx.Params.Get(key)
}
// ParamInt returns the int representation of the key's path named parameter's value
func (ctx *Context) ParamInt(key string) (int, error) {
val, err := strconv.Atoi(ctx.Param(key))
return val, err
}
// URLParam returns the get parameter from a request , if any
func (ctx *Context) URLParam(key string) string {
return string(ctx.RequestCtx.Request.URI().QueryArgs().Peek(key))
}
// URLParams returns a map of a list of each url(query) parameter
func (ctx *Context) URLParams() map[string]string {
urlparams := make(map[string]string)
ctx.RequestCtx.Request.URI().QueryArgs().VisitAll(func(key, value []byte) {
urlparams[string(key)] = string(value)
})
return urlparams
}
// URLParamInt returns the get parameter int value from a request , if any
func (ctx *Context) URLParamInt(key string) (int, error) {
return strconv.Atoi(ctx.URLParam(key))
}
// MethodString returns the HTTP Method
func (ctx *Context) MethodString() string {
return utils.BytesToString(ctx.Method())
}
// HostString returns the Host of the request( the url as string )
func (ctx *Context) HostString() string {
return utils.BytesToString(ctx.Host())
}
// PathString returns the full path as string
func (ctx *Context) PathString() string {
return utils.BytesToString(ctx.Path())
}
// RequestIP gets just the Remote Address from the client.
func (ctx *Context) RequestIP() string {
if ip, _, err := net.SplitHostPort(strings.TrimSpace(ctx.RequestCtx.RemoteAddr().String())); err == nil {
return ip
}
return ""
}
// RemoteAddr is like RequestIP but it checks for proxy servers also, tries to get the real client's request IP
func (ctx *Context) RemoteAddr() string {
header := string(ctx.RequestCtx.Request.Header.Peek("X-Real-Ip"))
realIP := strings.TrimSpace(header)
if realIP != "" {
return realIP
}
realIP = string(ctx.RequestCtx.Request.Header.Peek("X-Forwarded-For"))
idx := strings.IndexByte(realIP, ',')
if idx >= 0 {
realIP = realIP[0:idx]
}
realIP = strings.TrimSpace(realIP)
if realIP != "" {
return realIP
}
return ctx.RequestIP()
}
// RequestHeader returns the request header's value
// accepts one parameter, the key of the header (string)
// returns string
func (ctx *Context) RequestHeader(k string) string {
return utils.BytesToString(ctx.RequestCtx.Request.Header.Peek(k))
}
// PostFormValue returns a single value from post request's data
func (ctx *Context) PostFormValue(name string) string {
return string(ctx.RequestCtx.PostArgs().Peek(name))
}
/* Credits to Manish Singh @kryptodev for URLEncode */
// URLEncode returns the path encoded as url
// useful when you want to pass something to a database and be valid to retrieve it via context.Param
// use it only for special cases, when the default behavior doesn't suits you.
//
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
func URLEncode(path string) string {
if path == "" {
return ""
}
u := fasthttp.AcquireURI()
u.SetPath(path)
encodedPath := u.String()[8:]
fasthttp.ReleaseURI(u)
return encodedPath
}
// ReadJSON reads JSON from request's body
func (ctx *Context) ReadJSON(jsonObject interface{}) error {
return bindings.BindJSON(ctx, jsonObject)
}
// ReadXML reads XML from request's body
func (ctx *Context) ReadXML(xmlObject interface{}) error {
return bindings.BindXML(ctx, xmlObject)
}
// ReadForm binds the formObject with the form data
// it supports any kind of struct
func (ctx *Context) ReadForm(formObject interface{}) error {
return bindings.BindForm(ctx, formObject)
}

48
context_response.go Normal file
View File

@ -0,0 +1,48 @@
package iris
// SetContentType sets the response writer's header key 'Content-Type' to a given value(s)
func (ctx *Context) SetContentType(s string) {
ctx.RequestCtx.Response.Header.Set(ContentType, s)
}
// SetHeader write to the response writer's header to a given key the given value(s)
func (ctx *Context) SetHeader(k string, v string) {
ctx.RequestCtx.Response.Header.Set(k, v)
}
// Redirect redirect sends a redirect response the client
// accepts 2 parameters string and an optional int
// first parameter is the url to redirect
// second parameter is the http status should send, default is 302 (Temporary redirect), you can set it to 301 (Permant redirect), if that's nessecery
func (ctx *Context) Redirect(urlToRedirect string, statusHeader ...int) {
httpStatus := 302 // temporary redirect
if statusHeader != nil && len(statusHeader) > 0 && statusHeader[0] > 0 {
httpStatus = statusHeader[0]
}
ctx.RequestCtx.Redirect(urlToRedirect, httpStatus)
ctx.StopExecution()
}
// Error handling
// NotFound emits an error 404 to the client, using the custom http errors
// if no custom errors provided then it sends the default http.NotFound
func (ctx *Context) NotFound() {
ctx.StopExecution()
ctx.station.EmitError(404, ctx)
}
// Panic stops the executions of the context and returns the registed panic handler
// or if not, the default which is 500 http status to the client
//
// This function is useful when you use the recovery middleware, which is auto-executing the (custom, registed) 500 internal server error.
func (ctx *Context) Panic() {
ctx.StopExecution()
ctx.station.EmitError(500, ctx)
}
// EmitError executes the custom error by the http status code passed to the function
func (ctx *Context) EmitError(statusCode int) {
ctx.station.EmitError(statusCode, ctx)
}

157
context_storage.go Normal file
View File

@ -0,0 +1,157 @@
package iris
import (
"encoding/base64"
"time"
"github.com/kataras/iris/sessions/store"
"github.com/kataras/iris/utils"
"github.com/valyala/fasthttp"
)
// After v2.2.3 Get/GetFmt/GetString/GetInt/Set are all return values from the RequestCtx.userValues they are reseting on each connection.
// Get returns the user's value from a key
// if doesn't exists returns nil
func (ctx *Context) Get(key string) interface{} {
return ctx.RequestCtx.UserValue(key)
}
// GetFmt returns a value which has this format: func(format string, args ...interface{}) string
// if doesn't exists returns nil
func (ctx *Context) GetFmt(key string) func(format string, args ...interface{}) string {
if v, ok := ctx.Get(key).(func(format string, args ...interface{}) string); ok {
return v
}
return func(format string, args ...interface{}) string { return "" }
}
// GetString same as Get but returns the value as string
// if nothing founds returns empty string ""
func (ctx *Context) GetString(key string) string {
if v, ok := ctx.Get(key).(string); ok {
return v
}
return ""
}
// GetInt same as Get but returns the value as int
// if nothing founds returns -1
func (ctx *Context) GetInt(key string) int {
if v, ok := ctx.Get(key).(int); ok {
return v
}
return -1
}
// Set sets a value to a key in the values map
func (ctx *Context) Set(key string, value interface{}) {
ctx.RequestCtx.SetUserValue(key, value)
}
// GetCookie returns cookie's value by it's name
// returns empty string if nothing was found
func (ctx *Context) GetCookie(name string) (val string) {
bcookie := ctx.RequestCtx.Request.Header.Cookie(name)
if bcookie != nil {
val = string(bcookie)
}
return
}
// SetCookie adds a cookie
func (ctx *Context) SetCookie(cookie *fasthttp.Cookie) {
ctx.RequestCtx.Response.Header.SetCookie(cookie)
}
// SetCookieKV adds a cookie, receives just a key(string) and a value(string)
func (ctx *Context) SetCookieKV(key, value string) {
c := fasthttp.AcquireCookie() // &fasthttp.Cookie{}
c.SetKey(key)
c.SetValue(value)
c.SetHTTPOnly(true)
c.SetExpire(time.Now().Add(time.Duration(120) * time.Minute))
ctx.SetCookie(c)
fasthttp.ReleaseCookie(c)
}
// RemoveCookie deletes a cookie by it's name/key
func (ctx *Context) RemoveCookie(name string) {
cookie := fasthttp.AcquireCookie()
cookie.SetKey(name)
cookie.SetValue("")
cookie.SetPath("/")
cookie.SetHTTPOnly(true)
exp := time.Now().Add(-time.Duration(1) * time.Minute) //RFC says 1 second, but make sure 1 minute because we are using fasthttp
cookie.SetExpire(exp)
ctx.Response.Header.SetCookie(cookie)
fasthttp.ReleaseCookie(cookie)
}
// GetFlash get a flash message by it's key
// after this action the messages is removed
// returns string, if the cookie doesn't exists the string is empty
func (ctx *Context) GetFlash(key string) string {
val, err := ctx.GetFlashBytes(key)
if err != nil {
return ""
}
return string(val)
}
// GetFlashBytes get a flash message by it's key
// after this action the messages is removed
// returns []byte along with an error if the cookie doesn't exists or decode fails
func (ctx *Context) GetFlashBytes(key string) (value []byte, err error) {
cookieValue := string(ctx.RequestCtx.Request.Header.Cookie(key))
if cookieValue == "" {
err = ErrFlashNotFound.Return()
} else {
value, err = base64.URLEncoding.DecodeString(cookieValue)
//remove the message
ctx.RemoveCookie(key)
//it should'b be removed until the next reload, so we don't do that: ctx.Request.Header.SetCookie(key, "")
}
return
}
// SetFlash sets a flash message, accepts 2 parameters the key(string) and the value(string)
func (ctx *Context) SetFlash(key string, value string) {
ctx.SetFlashBytes(key, utils.StringToBytes(value))
}
// SetFlashBytes sets a flash message, accepts 2 parameters the key(string) and the value([]byte)
func (ctx *Context) SetFlashBytes(key string, value []byte) {
c := fasthttp.AcquireCookie()
c.SetKey(key)
c.SetValue(base64.URLEncoding.EncodeToString(value))
c.SetPath("/")
c.SetHTTPOnly(true)
ctx.RequestCtx.Response.Header.SetCookie(c)
fasthttp.ReleaseCookie(c)
}
// Sessionreturns the current session store, returns nil if provider is ""
func (ctx *Context) Session() store.IStore {
if ctx.station.sessionManager == nil || ctx.station.config.Sessions.Provider == "" { //the second check can be changed on runtime, users are able to turn off the sessions by setting provider to ""
return nil
}
if ctx.sessionStore == nil {
ctx.sessionStore = ctx.station.sessionManager.Start(ctx)
}
return ctx.sessionStore
}
// SessionDestroy destroys the whole session, calls the provider's destory and remove the cookie
func (ctx *Context) SessionDestroy() {
if ctx.station.sessionManager != nil {
if store := ctx.Session(); store != nil {
ctx.station.sessionManager.Destroy(ctx)
}
}
}

39
errors.go Normal file
View File

@ -0,0 +1,39 @@
package iris
import "github.com/kataras/iris/errors"
var (
// Router, Party & Handler
// ErrHandler returns na error with message: 'Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)
// It seems to be a +type Points to: +pointer.'
ErrHandler = errors.New("Passed argument is not func(*Context) neither an object which implements the iris.Handler with Serve(ctx *Context)\n It seems to be a %T Points to: %v.")
// ErrHandleAnnotated returns an error with message: 'HandleAnnotated parse: +specific error(s)'
ErrHandleAnnotated = errors.New("HandleAnnotated parse: %s")
ErrControllerContextNotFound = errors.New("Context *iris.Context could not be found, the Controller won't be registed.")
ErrDirectoryFileNotFound = errors.New("Directory or file %s couldn't found. Trace: %s")
// Plugin
// ErrPluginAlreadyExists returns an error with message: 'Cannot activate the same plugin again, plugin '+plugin name[+plugin description]' is already exists'
ErrPluginAlreadyExists = errors.New("Cannot use the same plugin again, '%s[%s]' is already exists")
// ErrPluginActivate returns an error with message: 'While trying to activate plugin '+plugin name'. Trace: +specific error'
ErrPluginActivate = errors.New("While trying to activate plugin '%s'. Trace: %s")
// ErrPluginRemoveNoPlugins returns an error with message: 'No plugins are registed yet, you cannot remove a plugin from an empty list!'
ErrPluginRemoveNoPlugins = errors.New("No plugins are registed yet, you cannot remove a plugin from an empty list!")
// ErrPluginRemoveEmptyName returns an error with message: 'Plugin with an empty name cannot be removed'
ErrPluginRemoveEmptyName = errors.New("Plugin with an empty name cannot be removed")
// ErrPluginRemoveNotFound returns an error with message: 'Cannot remove a plugin which doesn't exists'
ErrPluginRemoveNotFound = errors.New("Cannot remove a plugin which doesn't exists")
// Context other
// ErrServeContent returns an error with message: 'While trying to serve content to the client. Trace +specific error'
ErrServeContent = errors.New("While trying to serve content to the client. Trace %s")
// ErrTemplateExecute returns an error with message:'Unable to execute a template. Trace: +specific error'
ErrTemplateExecute = errors.New("Unable to execute a template. Trace: %s")
// ErrFlashNotFound returns an error with message: 'Unable to get flash message. Trace: Cookie does not exists'
ErrFlashNotFound = errors.New("Unable to get flash message. Trace: Cookie does not exists")
// ErrSessionNil returns an error with message: 'Unable to set session, Config().Session.Provider is nil, please refer to the docs!'
ErrSessionNil = errors.New("Unable to set session, Config().Session.Provider is nil, please refer to the docs!")
)

6
errors/README.md Normal file
View File

@ -0,0 +1,6 @@
## Package information
I decide to split the errors from the main iris package because error.go doesn't depends on any of the iris' types.
**Examples and more info will be added soon.**

72
errors/error.go Normal file
View File

@ -0,0 +1,72 @@
package errors
import (
"fmt"
"runtime"
"github.com/kataras/iris/logger"
)
// Error holds the error
type Error struct {
message string
}
// Error returns the message of the actual error
func (e *Error) Error() string {
return e.message
}
// Format returns a formatted new error based on the arguments
func (e *Error) Format(args ...interface{}) error {
return fmt.Errorf(e.message, args)
}
// With does the same thing as Format but it receives an error type which if it's nil it returns a nil error
func (e *Error) With(err error) error {
if err == nil {
return nil
}
return e.Format(err.Error())
}
// Return returns the actual error as it is
func (e *Error) Return() error {
return fmt.Errorf(e.message)
}
// Panic output the message and after panics
func (e *Error) Panic() {
if e == nil {
return
}
_, fn, line, _ := runtime.Caller(1)
errMsg := e.message
errMsg = "\nCaller was: " + fmt.Sprintf("%s:%d", fn, line)
panic(errMsg)
}
// Panicf output the formatted message and after panics
func (e *Error) Panicf(args ...interface{}) {
if e == nil {
return
}
_, fn, line, _ := runtime.Caller(1)
errMsg := e.Format(args...).Error()
errMsg = "\nCaller was: " + fmt.Sprintf("%s:%d", fn, line)
panic(errMsg)
}
//
// New creates and returns an Error with a message
func New(errMsg string) *Error {
// return &Error{fmt.Errorf("\n" + logger.Prefix + "Error: " + errMsg)}
return &Error{message: "\n" + logger.Prefix + " Error: " + errMsg}
}
// Printf prints to the logger a specific error with optionally arguments
func Printf(logger *logger.Logger, err error, args ...interface{}) {
logger.Printf(err.Error(), args...)
}

25
graceful/README.md Normal file
View File

@ -0,0 +1,25 @@
## Package information
Enables graceful shutdown.
## Usage
```go
package main
import (
"github.com/kataras/iris/graceful"
"github.com/kataras/iris"
"time"
)
func main() {
api := iris.New()
api.Get("/", func(c *iris.Context) {
c.Write("Welcome to the home page!")
})
graceful.Run(":3001", time.Duration(10)*time.Second, api)
}
```

292
graceful/graceful.go Normal file
View File

@ -0,0 +1,292 @@
package graceful
import (
"net"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/config"
"github.com/kataras/iris/logger"
"github.com/kataras/iris/server"
"golang.org/x/net/netutil"
)
// Server wraps an iris.Server with graceful connection handling.
// It may be used directly in the same way as iris.Server, or may
// be constructed with the global functions in this package.
type Server struct {
*server.Server
station *iris.Iris
// Timeout is the duration to allow outstanding requests to survive
// before forcefully terminating them.
Timeout time.Duration
// Limit the number of outstanding requests
ListenLimit int
// BeforeShutdown is an optional callback function that is called
// before the listener is closed.
BeforeShutdown func()
// ShutdownInitiated is an optional callback function that is called
// when shutdown is initiated. It can be used to notify the client
// side of long lived connections (e.g. websockets) to reconnect.
ShutdownInitiated func()
// NoSignalHandling prevents graceful from automatically shutting down
// on SIGINT and SIGTERM. If set to true, you must shut down the server
// manually with Stop().
NoSignalHandling bool
// Logger used to notify of errors on startup and on stop.
Logger *logger.Logger
// Interrupted is true if the server is handling a SIGINT or SIGTERM
// signal and is thus shutting down.
Interrupted bool
// interrupt signals the listener to stop serving connections,
// and the server to shut down.
interrupt chan os.Signal
// stopLock is used to protect against concurrent calls to Stop
stopLock sync.Mutex
// stopChan is the channel on which callers may block while waiting for
// the server to stop.
stopChan chan struct{}
// chanLock is used to protect access to the various channel constructors.
chanLock sync.RWMutex
// connections holds all connections managed by graceful
connections map[net.Conn]struct{}
}
// Run serves the http.Handler with graceful shutdown enabled.
//
// timeout is the duration to wait until killing active requests and stopping the server.
// If timeout is 0, the server never times out. It waits for all active requests to finish.
// we don't pass an iris.RequestHandler , because we need iris.station.server to be setted in order the station.Close() to work
func Run(addr string, timeout time.Duration, n *iris.Iris) {
srv := &Server{
Timeout: timeout,
Logger: DefaultLogger(),
}
srv.station = n
srv.Server = srv.station.PreListen(config.Server{ListeningAddr: addr})
if err := srv.listenAndServe(); err != nil {
if opErr, ok := err.(*net.OpError); !ok || (ok && opErr.Op != "accept") {
srv.Logger.Fatal(err)
}
}
}
// RunWithErr is an alternative version of Run function which can return error.
//
// Unlike Run this version will not exit the program if an error is encountered but will
// return it instead.
func RunWithErr(addr string, timeout time.Duration, n *iris.Iris) error {
srv := &Server{
Timeout: timeout,
Logger: DefaultLogger(),
}
srv.station = n
srv.Server = srv.station.PreListen(config.Server{ListeningAddr: addr})
return srv.listenAndServe()
}
// ListenAndServe is equivalent to iris.Listen with graceful shutdown enabled.
func (srv *Server) listenAndServe() error {
// Create the listener so we can control their lifetime
addr := srv.Config.ListeningAddr
if addr == "" {
addr = ":http"
}
l, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.serve(l)
}
// Serve is equivalent to iris.Server.Serve with graceful shutdown enabled.
func (srv *Server) serve(listener net.Listener) error {
if srv.ListenLimit != 0 {
listener = netutil.LimitListener(listener, srv.ListenLimit)
}
// Track connection state
add := make(chan net.Conn)
remove := make(chan net.Conn)
// Manage open connections
shutdown := make(chan chan struct{})
kill := make(chan struct{})
go srv.manageConnections(add, remove, shutdown, kill)
interrupt := srv.interruptChan()
// Set up the interrupt handler
if !srv.NoSignalHandling {
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
}
quitting := make(chan struct{})
go srv.handleInterrupt(interrupt, quitting, listener)
// Serve with graceful listener.
// Execution blocks here until listener.Close() is called, above.
srv.station.PostListen()
err := srv.Server.Serve(listener)
if err != nil {
// If the underlying listening is closed, Serve returns an error
// complaining about listening on a closed socket. This is expected, so
// let's ignore the error if we are the ones who explicitly closed the
// socket.
select {
case <-quitting:
err = nil
default:
}
}
srv.shutdown(shutdown, kill)
return err
}
// Stop instructs the type to halt operations and close
// the stop channel when it is finished.
//
// timeout is grace period for which to wait before shutting
// down the server. The timeout value passed here will override the
// timeout given when constructing the server, as this is an explicit
// command to stop the server.
func (srv *Server) Stop(timeout time.Duration) {
srv.stopLock.Lock()
defer srv.stopLock.Unlock()
srv.Timeout = timeout
interrupt := srv.interruptChan()
interrupt <- syscall.SIGINT
}
// StopChan gets the stop channel which will block until
// stopping has completed, at which point it is closed.
// Callers should never close the stop channel.
func (srv *Server) StopChan() <-chan struct{} {
srv.chanLock.Lock()
defer srv.chanLock.Unlock()
if srv.stopChan == nil {
srv.stopChan = make(chan struct{})
}
return srv.stopChan
}
// DefaultLogger returns the logger used by Run, RunWithErr, ListenAndServe, ListenAndServeTLS and Serve.
// The logger outputs to STDERR by default.
func DefaultLogger() *logger.Logger {
return logger.New()
}
func (srv *Server) manageConnections(add, remove chan net.Conn, shutdown chan chan struct{}, kill chan struct{}) {
var done chan struct{}
srv.connections = map[net.Conn]struct{}{}
for {
select {
case conn := <-add:
srv.connections[conn] = struct{}{}
case conn := <-remove:
delete(srv.connections, conn)
if done != nil && len(srv.connections) == 0 {
done <- struct{}{}
return
}
case done = <-shutdown:
if len(srv.connections) == 0 {
done <- struct{}{}
return
}
case <-kill:
for k := range srv.connections {
if err := k.Close(); err != nil {
srv.log("[IRIS GRACEFUL ERROR] %s", err.Error())
}
}
return
}
}
}
func (srv *Server) interruptChan() chan os.Signal {
srv.chanLock.Lock()
defer srv.chanLock.Unlock()
if srv.interrupt == nil {
srv.interrupt = make(chan os.Signal, 1)
}
return srv.interrupt
}
func (srv *Server) handleInterrupt(interrupt chan os.Signal, quitting chan struct{}, listener net.Listener) {
for _ = range interrupt {
if srv.Interrupted {
srv.log("already shutting down")
continue
}
srv.log("shutdown initiated")
srv.Interrupted = true
if srv.BeforeShutdown != nil {
srv.BeforeShutdown()
}
close(quitting)
srv.Server.DisableKeepalive = true
if err := listener.Close(); err != nil {
srv.log("[IRIS GRACEFUL ERROR] %s", err.Error())
}
if srv.ShutdownInitiated != nil {
srv.ShutdownInitiated()
}
}
}
func (srv *Server) log(fmt string, v ...interface{}) {
if srv.Logger != nil {
srv.Logger.Printf(fmt, v...)
}
}
func (srv *Server) shutdown(shutdown chan chan struct{}, kill chan struct{}) {
// Request done notification
done := make(chan struct{})
shutdown <- done
if srv.Timeout > 0 {
select {
case <-done:
case <-time.After(srv.Timeout):
close(kill)
}
} else {
<-done
}
// Close the stopChan to wake up any blocked goroutines.
srv.chanLock.Lock()
if srv.stopChan != nil {
close(srv.stopChan)
}
// notify the iris plugins
srv.station.Close()
srv.chanLock.Unlock()
}

119
handler.go Normal file
View File

@ -0,0 +1,119 @@
package iris
import (
"net/http"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttpadaptor"
)
type (
// Handler the main Iris Handler interface.
Handler interface {
Serve(ctx *Context)
}
// HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
HandlerFunc func(*Context)
// HandlerAPI allow the use of a custom struct as API handler(s) for a particular request path
// It's just an interface {}, we keep it here to make things more readable.
HandlerAPI interface {
// we don't use context.IContext because of some methods as Get() is already inside the IContext interface and conficts with the Get()
// we want to use for API.
// a valid controller has this form:
/*
type index struct {
*iris.Context
}
// OR
type index struct {
Context *iris.Context
}
func (i index) Get() {
i.Write("Hello from /")
}
func (i index) GetBy(id string) {} // /:namedParameter
//POST,PUT,DELETE...
*/
}
//IMiddlewareSupporter is an interface which all routers must implement
IMiddlewareSupporter interface {
Use(handlers ...Handler)
UseFunc(handlersFn ...HandlerFunc)
}
// Middleware is just a slice of Handler []func(c *Context)
Middleware []Handler
)
// Serve serves the handler, is like ServeHTTP for Iris
func (h HandlerFunc) Serve(ctx *Context) {
h(ctx)
}
// ToHandler converts an http.Handler or http.HandlerFunc to an iris.Handler
func ToHandler(handler interface{}) Handler {
//this is not the best way to do it, but I dont have any options right now.
switch handler.(type) {
case Handler:
//it's already an iris handler
return handler.(Handler)
case http.Handler:
//it's http.Handler
h := fasthttpadaptor.NewFastHTTPHandlerFunc(handler.(http.Handler).ServeHTTP)
return ToHandlerFastHTTP(h)
case func(http.ResponseWriter, *http.Request):
//it's http.HandlerFunc
h := fasthttpadaptor.NewFastHTTPHandlerFunc(handler.(func(http.ResponseWriter, *http.Request)))
return ToHandlerFastHTTP(h)
default:
panic(ErrHandler.Format(handler, handler))
}
}
// ToHandlerFunc converts an http.Handler or http.HandlerFunc to an iris.HandlerFunc
func ToHandlerFunc(handler interface{}) HandlerFunc {
return ToHandler(handler).Serve
}
// ToHandlerFastHTTP converts an fasthttp.RequestHandler to an iris.Handler
func ToHandlerFastHTTP(h fasthttp.RequestHandler) Handler {
return HandlerFunc((func(ctx *Context) {
h(ctx.RequestCtx)
}))
}
// ConvertToHandlers accepts list of HandlerFunc and returns list of Handler
// this can be renamed to convertToMiddleware also because it returns a list of []Handler which is what Middleware is
func ConvertToHandlers(handlersFn []HandlerFunc) []Handler {
hlen := len(handlersFn)
mlist := make([]Handler, hlen)
for i := 0; i < hlen; i++ {
mlist[i] = Handler(handlersFn[i])
}
return mlist
}
// JoinMiddleware uses to create a copy of all middleware and return them in order to use inside the node
func JoinMiddleware(middleware1 Middleware, middleware2 Middleware) Middleware {
nowLen := len(middleware1)
totalLen := nowLen + len(middleware2)
// create a new slice of middleware in order to store all handlers, the already handlers(middleware) and the new
newMiddleware := make(Middleware, totalLen)
//copy the already middleware to the just created
copy(newMiddleware, middleware1)
//start from there we finish, and store the new middleware too
copy(newMiddleware[nowLen:], middleware2)
return newMiddleware
}

228
httperror.go Normal file
View File

@ -0,0 +1,228 @@
package iris
//taken from net/http
const (
StatusContinue = 100
StatusSwitchingProtocols = 101
StatusOK = 200
StatusCreated = 201
StatusAccepted = 202
StatusNonAuthoritativeInfo = 203
StatusNoContent = 204
StatusResetContent = 205
StatusPartialContent = 206
StatusMultipleChoices = 300
StatusMovedPermanently = 301
StatusFound = 302
StatusSeeOther = 303
StatusNotModified = 304
StatusUseProxy = 305
StatusTemporaryRedirect = 307
StatusBadRequest = 400
StatusUnauthorized = 401
StatusPaymentRequired = 402
StatusForbidden = 403
StatusNotFound = 404
StatusMethodNotAllowed = 405
StatusNotAcceptable = 406
StatusProxyAuthRequired = 407
StatusRequestTimeout = 408
StatusConflict = 409
StatusGone = 410
StatusLengthRequired = 411
StatusPreconditionFailed = 412
StatusRequestEntityTooLarge = 413
StatusRequestURITooLong = 414
StatusUnsupportedMediaType = 415
StatusRequestedRangeNotSatisfiable = 416
StatusExpectationFailed = 417
StatusTeapot = 418
StatusPreconditionRequired = 428
StatusTooManyRequests = 429
StatusRequestHeaderFieldsTooLarge = 431
StatusUnavailableForLegalReasons = 451
StatusInternalServerError = 500
StatusNotImplemented = 501
StatusBadGateway = 502
StatusServiceUnavailable = 503
StatusGatewayTimeout = 504
StatusHTTPVersionNotSupported = 505
StatusNetworkAuthenticationRequired = 511
)
var statusText = map[int]string{
StatusContinue: "Continue",
StatusSwitchingProtocols: "Switching Protocols",
StatusOK: "OK",
StatusCreated: "Created",
StatusAccepted: "Accepted",
StatusNonAuthoritativeInfo: "Non-Authoritative Information",
StatusNoContent: "No Content",
StatusResetContent: "Reset Content",
StatusPartialContent: "Partial Content",
StatusMultipleChoices: "Multiple Choices",
StatusMovedPermanently: "Moved Permanently",
StatusFound: "Found",
StatusSeeOther: "See Other",
StatusNotModified: "Not Modified",
StatusUseProxy: "Use Proxy",
StatusTemporaryRedirect: "Temporary Redirect",
StatusBadRequest: "Bad Request",
StatusUnauthorized: "Unauthorized",
StatusPaymentRequired: "Payment Required",
StatusForbidden: "Forbidden",
StatusNotFound: "Not Found",
StatusMethodNotAllowed: "Method Not Allowed",
StatusNotAcceptable: "Not Acceptable",
StatusProxyAuthRequired: "Proxy Authentication Required",
StatusRequestTimeout: "Request Timeout",
StatusConflict: "Conflict",
StatusGone: "Gone",
StatusLengthRequired: "Length Required",
StatusPreconditionFailed: "Precondition Failed",
StatusRequestEntityTooLarge: "Request Entity Too Large",
StatusRequestURITooLong: "Request URI Too Long",
StatusUnsupportedMediaType: "Unsupported Media Type",
StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
StatusExpectationFailed: "Expectation Failed",
StatusTeapot: "I'm a teapot",
StatusPreconditionRequired: "Precondition Required",
StatusTooManyRequests: "Too Many Requests",
StatusRequestHeaderFieldsTooLarge: "Request Header Fields Too Large",
StatusUnavailableForLegalReasons: "Unavailable For Legal Reasons",
StatusInternalServerError: "Internal Server Error",
StatusNotImplemented: "Not Implemented",
StatusBadGateway: "Bad Gateway",
StatusServiceUnavailable: "Service Unavailable",
StatusGatewayTimeout: "Gateway Timeout",
StatusHTTPVersionNotSupported: "HTTP Version Not Supported",
StatusNetworkAuthenticationRequired: "Network Authentication Required",
}
// StatusText returns a text for the HTTP status code. It returns the empty
// string if the code is unknown.
func StatusText(code int) string {
return statusText[code]
}
//
type (
// HTTPErrorHandler is just an object which stores a http status code and a handler
HTTPErrorHandler struct {
code int
handler HandlerFunc
}
// HTTPErrorContainer is the struct which contains the handlers which will execute if http error occurs
// One struct per Server instance, the meaning of this is that the developer can change the default error message and replace them with his/her own completely custom handlers
//
// Example of usage:
// iris.OnError(405, func (ctx *iris.Context){ c.SendStatus(405,"Method not allowed!!!")})
// and inside the handler which you have access to the current Context:
// ctx.EmitError(405)
HTTPErrorContainer struct {
// Errors contains all the httperrorhandlers
Errors []*HTTPErrorHandler
}
)
// HTTPErrorHandlerFunc creates a handler which is responsible to send a particular error to the client
func HTTPErrorHandlerFunc(statusCode int, message string) HandlerFunc {
return func(ctx *Context) {
ctx.SetStatusCode(statusCode)
ctx.SetBodyString(message)
}
}
// GetCode returns the http status code value
func (e *HTTPErrorHandler) GetCode() int {
return e.code
}
// GetHandler returns the handler which is type of HandlerFunc
func (e *HTTPErrorHandler) GetHandler() HandlerFunc {
return e.handler
}
// SetHandler sets the handler (type of HandlerFunc) to this particular ErrorHandler
func (e *HTTPErrorHandler) SetHandler(h HandlerFunc) {
e.handler = h
}
// defaultHTTPErrors creates and returns an instance of HTTPErrorContainer with default handlers
func defaultHTTPErrors() *HTTPErrorContainer {
httperrors := new(HTTPErrorContainer)
httperrors.Errors = make([]*HTTPErrorHandler, 0)
httperrors.OnError(StatusNotFound, HTTPErrorHandlerFunc(StatusNotFound, statusText[StatusNotFound]))
httperrors.OnError(StatusInternalServerError, HTTPErrorHandlerFunc(StatusInternalServerError, statusText[StatusInternalServerError]))
return httperrors
}
// GetByCode returns the error handler by it's http status code
func (he *HTTPErrorContainer) GetByCode(httpStatus int) *HTTPErrorHandler {
if he != nil {
for _, h := range he.Errors {
if h.GetCode() == httpStatus {
return h
}
}
}
return nil
}
// OnError Registers a handler for a specific http error status
func (he *HTTPErrorContainer) OnError(httpStatus int, handler HandlerFunc) {
if httpStatus == StatusOK {
return
}
if errH := he.GetByCode(httpStatus); errH != nil {
errH.SetHandler(handler)
} else {
he.Errors = append(he.Errors, &HTTPErrorHandler{code: httpStatus, handler: handler})
}
}
///TODO: the errors must have .Next too, as middlewares inside the Context, if I let it as it is then we have problem
// we cannot set a logger and a custom handler at one error because now the error handler takes only one handelrFunc and executes there from here...
// EmitError executes the handler of the given error http status code
func (he *HTTPErrorContainer) EmitError(errCode int, ctx *Context) {
if errHandler := he.GetByCode(errCode); errHandler != nil {
ctx.SetStatusCode(errCode) // for any case, user can change it after if want to
errHandler.GetHandler().Serve(ctx)
} else {
//if no error is registed, then register it with the default http error text, and re-run the Emit
he.OnError(errCode, func(c *Context) {
c.SetStatusCode(errCode)
c.SetBodyString(StatusText(errCode))
})
he.EmitError(errCode, ctx)
}
}
// OnNotFound sets the handler for http status 404,
// default is a response with text: 'Not Found' and status: 404
func (he *HTTPErrorContainer) OnNotFound(handlerFunc HandlerFunc) {
he.OnError(StatusNotFound, handlerFunc)
}
// OnPanic sets the handler for http status 500,
// default is a response with text: The server encountered an unexpected condition which prevented it from fulfilling the request. and status: 500
func (he *HTTPErrorContainer) OnPanic(handlerFunc HandlerFunc) {
he.OnError(StatusInternalServerError, handlerFunc)
}

313
iris.go Normal file
View File

@ -0,0 +1,313 @@
// Package iris v3.0.0-beta
//
// Note: When 'Station', we mean the Iris type.
package iris
import (
"os"
"sync"
"time"
"strconv"
"github.com/fatih/color"
"github.com/kataras/iris/config"
"github.com/kataras/iris/logger"
"github.com/kataras/iris/render/rest"
"github.com/kataras/iris/render/template"
"github.com/kataras/iris/server"
"github.com/kataras/iris/sessions"
_ "github.com/kataras/iris/sessions/providers/memory"
_ "github.com/kataras/iris/sessions/providers/redis"
"github.com/kataras/iris/utils"
"github.com/kataras/iris/websocket"
"github.com/klauspost/compress/gzip"
)
const (
Version = "v3.0.0-beta"
banner = ` _____ _
|_ _| (_)
| | ____ _ ___
| | | __|| |/ __|
_| |_| | | |\__ \
|_____|_| |_||___/
`
)
/* for conversion */
var (
HTMLEngine = config.HTMLEngine
PongoEngine = config.PongoEngine
MarkdownEngine = config.MarkdownEngine
JadeEngine = config.JadeEngine
AmberEngine = config.AmberEngine
DefaultEngine = config.DefaultEngine
NoEngine = config.NoEngine
//
NoLayout = config.NoLayout
)
/* */
var stationsRunning = 0
type (
// Iris is the container of all, server, router, cache and the sync.Pool
Iris struct {
*router
config *config.Iris
server *server.Server
plugins *PluginContainer
rest *rest.Render
templates *template.Template
sessionManager *sessions.Manager
websocketServer websocket.Server
logger *logger.Logger
gzipWriterPool sync.Pool // this pool is used everywhere needed in the iris for example inside party-> StaticSimple
}
)
// New creates and returns a new iris station.
//
// Receives an optional config.Iris as parameter
// If empty then config.Default() is used instead
func New(cfg ...config.Iris) *Iris {
c := config.Default().Merge(cfg)
// create the Iris
s := &Iris{config: &c, plugins: &PluginContainer{}}
// create & set the router
s.router = newRouter(s)
// set the Logger
s.logger = logger.New()
// set the gzip writer pool
s.gzipWriterPool = sync.Pool{New: func() interface{} { return &gzip.Writer{} }}
return s
}
// newContextPool returns a new context pool, internal method used in tree and router
func (s *Iris) newContextPool() sync.Pool {
return sync.Pool{New: func() interface{} {
return &Context{station: s}
}}
}
func (s *Iris) initTemplates() {
if s.templates == nil { // because if .Templates() called before server's listen, s.templates != nil
// init the templates
s.templates = template.New(s.config.Render.Template)
}
}
func (s *Iris) initWebsocketServer() {
if s.websocketServer == nil {
// enable websocket if config.Websocket.Endpoint != ""
if s.config.Websocket.Endpoint != "" {
s.websocketServer = websocket.New(s, s.config.Websocket)
}
}
}
func (s *Iris) printBanner() {
c := color.New(color.FgHiBlue).Add(color.Bold)
printTicker := utils.NewTicker()
i := 0
printTicker.OnTick(func() {
c.Printf("%c", banner[i])
i++
if i == len(banner) {
printTicker.Stop()
c.Add(color.FgGreen)
stationsRunning++
c.Println()
if stationsRunning > 1 {
c.Println("Server[" + strconv.Itoa(stationsRunning) + "]")
}
c.Printf("%s: Running at %s\n", time.Now().Format(config.TimeFormat), s.server.Config.ListeningAddr)
c.DisableColor()
}
})
printTicker.Start(time.Duration(2) * time.Millisecond)
}
// PreListen call router's optimize, sets the server's handler and notice the plugins
// capital because we need it sometimes, for example inside the graceful
// receives the config.Server
// returns the station's Server (*server.Server)
// it's a non-blocking func
func (s *Iris) PreListen(opt config.Server) *server.Server {
// set the logger's state
s.logger.SetEnable(!s.config.DisableLog)
// router preparation, runs only once even if called more than one time.
if !s.router.optimized {
s.router.optimize()
s.server = server.New(opt)
s.server.SetHandler(s.router.ServeRequest)
if s.config.MaxRequestBodySize > 0 {
s.server.MaxRequestBodySize = int(s.config.MaxRequestBodySize)
}
}
s.plugins.DoPreListen(s)
return s.server
}
// PostListen sets the rest render, template engine, sessions and notice the plugins
// capital because we need it sometimes, for example inside the graceful
// it's a non-blocking func
func (s *Iris) PostListen() {
//if not error opening the server, then:
//set the rest (for Data, Text, JSON, JSONP, XML)
s.rest = rest.New(s.config.Render.Rest)
// set the templates
s.initTemplates()
// set the session manager if we have a provider
if s.config.Sessions.Provider != "" {
s.sessionManager = sessions.New(s.config.Sessions)
}
// set the websocket
s.initWebsocketServer()
if !s.config.DisableBanner {
s.printBanner()
}
s.plugins.DoPostListen(s)
}
// listen is internal method, open the server with specific options passed by the Listen and ListenTLS
// it's a blocking func
func (s *Iris) listen(opt config.Server) (err error) {
s.PreListen(opt)
if err = s.server.OpenServer(); err == nil {
s.PostListen()
ch := make(chan os.Signal)
<-ch
s.Close()
}
return
}
// ListenWithErr starts the standalone http server
// which listens to the addr parameter which as the form of
// host:port or just port
//
// It returns an error you are responsible how to handle this
// if you need a func to panic on error use the Listen
// ex: log.Fatal(iris.ListenWithErr(":8080"))
func (s *Iris) ListenWithErr(addr string) error {
opt := config.Server{ListeningAddr: addr}
return s.listen(opt)
}
// Listen starts the standalone http server
// which listens to the addr parameter which as the form of
// host:port or just port
//
// It panics on error if you need a func to return an error use the ListenWithErr
// ex: iris.Listen(":8080")
func (s *Iris) Listen(addr string) {
if err := s.ListenWithErr(addr); err != nil {
panic(err)
}
}
// ListenTLSWithErr Starts a https server with certificates,
// if you use this method the requests of the form of 'http://' will fail
// only https:// connections are allowed
// which listens to the addr parameter which as the form of
// host:port or just port
//
// It returns an error you are responsible how to handle this
// if you need a func to panic on error use the ListenTLS
// ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key"))
func (s *Iris) ListenTLSWithErr(addr string, certFile, keyFile string) error {
opt := config.Server{ListeningAddr: addr, CertFile: certFile, KeyFile: keyFile}
return s.listen(opt)
}
// ListenTLS Starts a https server with certificates,
// if you use this method the requests of the form of 'http://' will fail
// only https:// connections are allowed
// which listens to the addr parameter which as the form of
// host:port or just port
//
// It panics on error if you need a func to return an error use the ListenTLSWithErr
// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key")
func (s *Iris) ListenTLS(addr string, certFile, keyFile string) {
if err := s.ListenTLSWithErr(addr, certFile, keyFile); err != nil {
panic(err)
}
}
// CloseWithErr is used to close the tcp listener from the server, returns an error
func (s *Iris) CloseWithErr() error {
s.plugins.DoPreClose(s)
return s.server.CloseServer()
}
//Close terminates the server and panic if error occurs
func (s *Iris) Close() {
if err := s.CloseWithErr(); err != nil {
panic(err)
}
}
// Server returns the server
func (s *Iris) Server() *server.Server {
return s.server
}
// Plugins returns the plugin container
func (s *Iris) Plugins() *PluginContainer {
return s.plugins
}
// Config returns the configs
func (s *Iris) Config() *config.Iris {
return s.config
}
// Logger returns the logger
func (s *Iris) Logger() *logger.Logger {
return s.logger
}
// Render returns the rest render
func (s *Iris) Rest() *rest.Render {
return s.rest
}
// Templates returns the template render
func (s *Iris) Templates() *template.Template {
s.initTemplates() // for any case the user called .Templates() before server's listen
return s.templates
}
// Websocket returns the websocket server
func (s *Iris) Websocket() websocket.Server {
s.initWebsocketServer() // for any case the user called .Websocket() before server's listen
return s.websocketServer
}

52
iris/README.md Normal file
View File

@ -0,0 +1,52 @@
## Package information
This package is the command line tool for [../](https://github.com/kataras/iris).
## Install
Current version: 0.0.1
```sh
go get -u github.com/kataras/iris/iris
```
## Usage
```sh
$ iris [command] [-flags]
```
> Note that you must have $GOPATH/bin to your $PATH system/environment variable.
## Create
**The create command** creates for you a start project in a directory
```sh
iris create
```
Will create the starter/basic project structure to the current working directory and run the app.
```sh
iris create -d C:\Users\kataras\Desktop\test1
```
Will create the starter/basic project structure to the C:\Users\kataras\Desktop\test1 folder and run the app.
## Version
```sh
iris version
```
Will print the current iris' installed version to your machine
## TODO
A lot more

12
iris/doc.go Normal file
View File

@ -0,0 +1,12 @@
package main
/*
go get -u github.com/kataras/iris/iris
create an empty folder, open the command prompt/terminal there, type and press enter:
iris create
*/

116
iris/main.go Normal file
View File

@ -0,0 +1,116 @@
package main
import (
"os"
"strings"
"runtime"
"github.com/fatih/color"
"github.com/kataras/cli"
"github.com/kataras/iris"
"github.com/kataras/iris/utils"
)
const (
PackagesURL = "https://github.com/iris-contrib/iris-command-assets/archive/master.zip"
PackagesExportedName = "iris-command-assets-master"
)
var (
app *cli.App
SuccessPrint = color.New(color.FgGreen).Add(color.Bold).PrintfFunc()
InfoPrint = color.New(color.FgHiCyan).Add(color.Bold).PrintfFunc()
packagesInstallDir = os.Getenv("GOPATH") + utils.PathSeparator + "src" + utils.PathSeparator + "github.com" + utils.PathSeparator + "kataras" + utils.PathSeparator + "iris" + utils.PathSeparator + "iris" + utils.PathSeparator
packagesDir = packagesInstallDir + PackagesExportedName + utils.PathSeparator
)
func init() {
app = cli.NewApp("iris", "Command line tool for Iris web framework", "0.0.1")
app.Command(cli.Command("version", "\t prints your iris version").Action(func(cli.Flags) error { app.Printf("%s", iris.Version); return nil }))
createCmd := cli.Command("create", "create a project to a given directory").
Flag("dir", "./", "-d ./ creates an iris starter kit to the current directory").
Flag("type", "basic", "-t basic creates the project based on the t 'package'").
Action(create)
app.Command(createCmd)
}
func main() {
app.Run(func(cli.Flags) error { return nil })
}
func create(flags cli.Flags) (err error) {
if !utils.DirectoryExists(packagesDir) {
downloadPackages()
}
targetDir := flags.String("dir")
if strings.HasPrefix(targetDir, "./") || strings.HasPrefix(targetDir, "."+utils.PathSeparator) {
currentWdir, err := os.Getwd()
if err != nil {
return err
}
targetDir = currentWdir + utils.PathSeparator + targetDir[2:]
}
createPackage(flags.String("type"), targetDir)
return
}
func downloadPackages() {
_, err := utils.Install("https://github.com/iris-contrib/iris-command-assets/archive/master.zip", packagesInstallDir)
if err != nil {
app.Printf("\nProblem while downloading the assets from the internet for the first time. Trace: %s", err.Error())
}
}
func createPackage(packageName string, targetDir string) error {
basicDir := packagesDir + packageName
err := utils.CopyDir(basicDir, targetDir)
if err != nil {
app.Printf("\nProblem while copying the %s package to the %s. Trace: %s", packageName, targetDir, err.Error())
return err
}
InfoPrint("\n%s package was installed successfully", packageName)
//run the server
// go build
buildCmd := utils.CommandBuilder("go", "build")
if targetDir[len(targetDir)-1] != os.PathSeparator || targetDir[len(targetDir)-1] != '/' {
targetDir += utils.PathSeparator
}
buildCmd.Dir = targetDir + "backend"
buildCmd.Stderr = os.Stderr
err = buildCmd.Start()
if err != nil {
app.Printf("\n Failed to build the %s package. Trace: %s", packageName, err.Error())
}
buildCmd.Wait()
println("\n")
// run backend/backend.exe
executable := "backend"
if runtime.GOOS == "windows" {
executable += ".exe"
}
runCmd := utils.CommandBuilder("." + utils.PathSeparator + executable)
runCmd.Dir = buildCmd.Dir
runCmd.Stdout = os.Stdout
runCmd.Stderr = os.Stderr
err = runCmd.Start()
if err != nil {
app.Printf("\n Failed to run the %s package. Trace: %s", packageName, err.Error())
}
runCmd.Wait()
return err
}

395
iris_singleton.go Normal file
View File

@ -0,0 +1,395 @@
package iris
import (
"github.com/kataras/iris/config"
"github.com/kataras/iris/logger"
"github.com/kataras/iris/render/rest"
"github.com/kataras/iris/render/template"
"github.com/kataras/iris/server"
"github.com/kataras/iris/websocket"
)
// DefaultIris in order to use iris.Get(...,...) we need a default Iris on the package level
var DefaultIris *Iris = New()
// Listen starts the standalone http server
// which listens to the addr parameter which as the form of
// host:port or just port
//
// It panics on error if you need a func to return an error use the ListenWithErr
// ex: iris.Listen(":8080")
func Listen(addr string) {
DefaultIris.Listen(addr)
}
// ListenWithErr starts the standalone http server
// which listens to the addr parameter which as the form of
// host:port or just port
//
// It returns an error you are responsible how to handle this
// if you need a func to panic on error use the Listen
// ex: log.Fatal(iris.ListenWithErr(":8080"))
func ListenWithErr(addr string) error {
return DefaultIris.ListenWithErr(addr)
}
// ListenTLS Starts a https server with certificates,
// if you use this method the requests of the form of 'http://' will fail
// only https:// connections are allowed
// which listens to the addr parameter which as the form of
// host:port or just port
//
// It panics on error if you need a func to return an error use the ListenTLSWithErr
// ex: iris.ListenTLS(":8080","yourfile.cert","yourfile.key")
func ListenTLS(addr string, certFile, keyFile string) {
DefaultIris.ListenTLS(addr, certFile, keyFile)
}
// ListenTLSWithErr Starts a https server with certificates,
// if you use this method the requests of the form of 'http://' will fail
// only https:// connections are allowed
// which listens to the addr parameter which as the form of
// host:port or just port
//
// It returns an error you are responsible how to handle this
// if you need a func to panic on error use the ListenTLS
// ex: log.Fatal(iris.ListenTLSWithErr(":8080","yourfile.cert","yourfile.key"))
func ListenTLSWithErr(addr string, certFile, keyFile string) error {
return DefaultIris.ListenTLSWithErr(addr, certFile, keyFile)
}
// Close is used to close the net.Listener of the standalone http server which has already running via .Listen
func Close() { DefaultIris.Close() }
// Router implementation
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
// Party can also be named as 'Join' or 'Node' or 'Group' , Party chosen because it has more fun
func Party(path string, handlersFn ...HandlerFunc) IParty {
return DefaultIris.Party(path, handlersFn...)
}
// Handle registers a route to the server's router
// if empty method is passed then registers handler(s) for all methods, same as .Any
func Handle(method string, registedPath string, handlers ...Handler) {
DefaultIris.Handle(method, registedPath, handlers...)
}
// HandleFunc registers a route with a method, path string, and a handler
func HandleFunc(method string, path string, handlersFn ...HandlerFunc) {
DefaultIris.HandleFunc(method, path, handlersFn...)
}
// HandleAnnotated registers a route handler using a Struct implements iris.Handler (as anonymous property)
// which it's metadata has the form of
// `method:"path"` and returns the route and an error if any occurs
// handler is passed by func(urstruct MyStruct) Serve(ctx *Context) {}
//
// HandleAnnotated will be deprecated until the final v3 !
func HandleAnnotated(irisHandler Handler) error {
return DefaultIris.HandleAnnotated(irisHandler)
}
// API converts & registers a custom struct to the router
// receives three parameters
// first is the request path (string)
// second is the custom struct (interface{}) which can be anything that has a *iris.Context as field
// third are the common middlewares, is optional parameter
//
// Recommend to use when you retrieve data from an external database,
// and the router-performance is not the (only) thing which slows the server's overall performance.
//
// This is a slow method, if you care about router-performance use the Handle/HandleFunc/Get/Post/Put/Delete/Trace/Options... instead
//
// Usage:
// All the below methods are optional except the *iris.Context field,
// example with /users :
/*
package main
import (
"github.com/kataras/iris"
)
type UserAPI struct {
*iris.Context
}
// GET /users
func (u UserAPI) Get() {
u.Write("Get from /users")
// u.JSON(iris.StatusOK,myDb.AllUsers())
}
// GET /:param1 which its value passed to the id argument
func (u UserAPI) GetBy(id string) { // id equals to u.Param("param1")
u.Write("Get from /users/%s", id)
// u.JSON(iris.StatusOK, myDb.GetUserById(id))
}
// PUT /users
func (u UserAPI) Put() {
name := u.FormValue("name")
// myDb.InsertUser(...)
println(string(name))
println("Put from /users")
}
// POST /users/:param1
func (u UserAPI) PostBy(id string) {
name := u.FormValue("name") // you can still use the whole Context's features!
// myDb.UpdateUser(...)
println(string(name))
println("Post from /users/" + id)
}
// DELETE /users/:param1
func (u UserAPI) DeleteBy(id string) {
// myDb.DeleteUser(id)
println("Delete from /" + id)
}
func main() {
iris.API("/users", UserAPI{})
iris.Listen(":80")
}
*/
func API(registedPath string, controller HandlerAPI, middlewares ...HandlerFunc) error {
return DefaultIris.API(registedPath, controller, middlewares...)
}
// Use appends a middleware to the route or to the router if it's called from router
func Use(handlers ...Handler) {
DefaultIris.Use(handlers...)
}
// UseFunc same as Use but it accepts/receives ...HandlerFunc instead of ...Handler
// form of acceptable: func(c *iris.Context){//first middleware}, func(c *iris.Context){//second middleware}
func UseFunc(handlersFn ...HandlerFunc) {
DefaultIris.UseFunc(handlersFn...)
}
// Get registers a route for the Get http method
func Get(path string, handlersFn ...HandlerFunc) {
DefaultIris.Get(path, handlersFn...)
}
// Post registers a route for the Post http method
func Post(path string, handlersFn ...HandlerFunc) {
DefaultIris.Post(path, handlersFn...)
}
// Put registers a route for the Put http method
func Put(path string, handlersFn ...HandlerFunc) {
DefaultIris.Put(path, handlersFn...)
}
// Delete registers a route for the Delete http method
func Delete(path string, handlersFn ...HandlerFunc) {
DefaultIris.Delete(path, handlersFn...)
}
// Connect registers a route for the Connect http method
func Connect(path string, handlersFn ...HandlerFunc) {
DefaultIris.Connect(path, handlersFn...)
}
// Head registers a route for the Head http method
func Head(path string, handlersFn ...HandlerFunc) {
DefaultIris.Head(path, handlersFn...)
}
// Options registers a route for the Options http method
func Options(path string, handlersFn ...HandlerFunc) {
DefaultIris.Options(path, handlersFn...)
}
// Patch registers a route for the Patch http method
func Patch(path string, handlersFn ...HandlerFunc) {
DefaultIris.Patch(path, handlersFn...)
}
// Trace registers a route for the Trace http methodd
func Trace(path string, handlersFn ...HandlerFunc) {
DefaultIris.Trace(path, handlersFn...)
}
// Any registers a route for ALL of the http methods (Get,Post,Put,Head,Patch,Options,Connect,Delete)
func Any(path string, handlersFn ...HandlerFunc) {
DefaultIris.Any(path, handlersFn...)
}
// StaticHandlerFunc returns a HandlerFunc to serve static system directory
// Accepts 5 parameters
//
// first is the systemPath (string)
// Path to the root directory to serve files from.
//
// second is the stripSlashes (int) level
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
//
// third is the compress (bool)
// Transparently compresses responses if set to true.
//
// The server tries minimizing CPU usage by caching compressed files.
// It adds FSCompressedFileSuffix suffix to the original file name and
// tries saving the resulting compressed file under the new file name.
// So it is advisable to give the server write access to Root
// and to all inner folders in order to minimze CPU usage when serving
// compressed responses.
//
// fourth is the generateIndexPages (bool)
// Index pages for directories without files matching IndexNames
// are automatically generated if set.
//
// Directory index generation may be quite slow for directories
// with many files (more than 1K), so it is discouraged enabling
// index pages' generation for such directories.
//
// fifth is the indexNames ([]string)
// List of index file names to try opening during directory access.
//
// For example:
//
// * index.html
// * index.htm
// * my-super-index.xml
//
func StaticHandlerFunc(systemPath string, stripSlashes int, compress bool, generateIndexPages bool, indexNames []string) HandlerFunc {
return DefaultIris.StaticHandlerFunc(systemPath, stripSlashes, compress, generateIndexPages, indexNames)
}
// Static registers a route which serves a system directory
// this doesn't generates an index page which list all files
// no compression is used also, for these features look at StaticFS func
// accepts three parameters
// first parameter is the request url path (string)
// second parameter is the system directory (string)
// third parameter is the level (int) of stripSlashes
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
func Static(reqPath string, systemPath string, stripSlashes int) {
DefaultIris.Static(reqPath, systemPath, stripSlashes)
}
// StaticFS registers a route which serves a system directory
// generates an index page which list all files
// uses compression which file cache, if you use this method it will generate compressed files also
// think this function as small fileserver with http
// accepts three parameters
// first parameter is the request url path (string)
// second parameter is the system directory (string)
// third parameter is the level (int) of stripSlashes
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
func StaticFS(reqPath string, systemPath string, stripSlashes int) {
DefaultIris.StaticFS(reqPath, systemPath, stripSlashes)
}
// StaticWeb same as Static but if index.html exists and request uri is '/' then display the index.html's contents
// accepts three parameters
// first parameter is the request url path (string)
// second parameter is the system directory (string)
// third parameter is the level (int) of stripSlashes
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
func StaticWeb(reqPath string, systemPath string, stripSlashes int) {
DefaultIris.StaticWeb(reqPath, systemPath, stripSlashes)
}
// StaticServe serves a directory as web resource
// it's the simpliest form of the Static* functions
// Almost same usage as StaticWeb
// accepts only one required parameter which is the systemPath ( the same path will be used to register the GET&HEAD routes)
// if second parameter is empty, otherwise the requestPath is the second parameter
// it uses gzip compression (compression on each request, no file cache)
func StaticServe(systemPath string, requestPath ...string) {
DefaultIris.StaticServe(systemPath, requestPath...)
}
// Favicon serves static favicon
// accepts 2 parameters, second is optionally
// favPath (string), declare the system directory path of the __.ico
// requestPath (string), it's the route's path, by default this is the "/favicon.ico" because some browsers tries to get this by default first,
// you can declare your own path if you have more than one favicon (desktop, mobile and so on)
//
// this func will add a route for you which will static serve the /yuorpath/yourfile.ico to the /yourfile.ico (nothing special that you can't handle by yourself)
// Note that you have to call it on every favicon you have to serve automatically (dekstop, mobile and so on)
//
// returns an error if something goes bad
func Favicon(favPath string, requestPath ...string) error {
return DefaultIris.Favicon(favPath)
}
// StaticContent serves bytes, memory cached, on the reqPath
func StaticContent(reqPath string, contentType string, content []byte) {
DefaultIris.StaticContent(reqPath, contentType, content)
}
// OnError Registers a handler for a specific http error status
func OnError(httpStatus int, handler HandlerFunc) {
DefaultIris.OnError(httpStatus, handler)
}
// EmitError executes the handler of the given error http status code
func EmitError(httpStatus int, ctx *Context) {
DefaultIris.EmitError(httpStatus, ctx)
}
// OnNotFound sets the handler for http status 404,
// default is a response with text: 'Not Found' and status: 404
func OnNotFound(handlerFunc HandlerFunc) {
DefaultIris.OnNotFound(handlerFunc)
}
// OnPanic sets the handler for http status 500,
// default is a response with text: The server encountered an unexpected condition which prevented it from fulfilling the request. and status: 500
func OnPanic(handlerFunc HandlerFunc) {
DefaultIris.OnPanic(handlerFunc)
}
// ***********************
// Export DefaultIris's exported properties
// ***********************
// Server returns the server
func Server() *server.Server {
return DefaultIris.Server()
}
// Plugins returns the plugin container
func Plugins() *PluginContainer {
return DefaultIris.Plugins()
}
// Config returns the configs
func Config() *config.Iris {
return DefaultIris.Config()
}
// Logger returns the logger
func Logger() *logger.Logger {
return DefaultIris.Logger()
}
// Rest returns the rest render
func Rest() *rest.Render {
return DefaultIris.Rest()
}
// Templates returns the template render
func Templates() *template.Template {
return DefaultIris.Templates()
}
// Websocket returns the websocket server
func Websocket() websocket.Server {
return DefaultIris.Websocket()
}

6
logger/README.md Normal file
View File

@ -0,0 +1,6 @@
## Package information
I decide to split the logger from the main iris package because logger.go doesn't depends on any of the iris' types.
**Examples and more info will be added soon.**

111
logger/logger.go Normal file
View File

@ -0,0 +1,111 @@
package logger
import (
"log"
"os"
"github.com/kataras/iris/config"
)
var (
// Prefix is the prefix for the logger, default is [IRIS]
Prefix = "[IRIS] "
)
// Logger is just a log.Logger
type Logger struct {
Logger *log.Logger
enabled bool
}
// New creates a new Logger. The out variable sets the
// destination to which log data will be written.
// The prefix appears at the beginning of each generated log line.
// The flag argument defines the logging properties.
func New(cfg ...config.Logger) *Logger {
c := config.DefaultLogger().Merge(cfg)
return &Logger{Logger: log.New(c.Out, Prefix+c.Prefix, c.Flag), enabled: true}
}
// SetEnable true enables, false disables the Logger
func (l *Logger) SetEnable(enable bool) {
l.enabled = enable
}
// IsEnabled returns true if Logger is enabled, otherwise false
func (l *Logger) IsEnabled() bool {
return l.enabled
}
// Print calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Print.
func (l *Logger) Print(v ...interface{}) {
if l.enabled {
l.Logger.Print(v...)
}
}
// Printf calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Printf.
func (l *Logger) Printf(format string, a ...interface{}) {
if l.enabled {
l.Logger.Printf(format, a...)
}
}
// Println calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Println.
func (l *Logger) Println(a ...interface{}) {
if l.enabled {
l.Logger.Println(a...)
}
}
// Fatal is equivalent to l.Print() followed by a call to os.Exit(1).
func (l *Logger) Fatal(a ...interface{}) {
if l.enabled {
l.Logger.Fatal(a...)
} else {
os.Exit(1) //we have to exit at any case because this is the Fatal
}
}
// Fatalf is equivalent to l.Printf() followed by a call to os.Exit(1).
func (l *Logger) Fatalf(format string, a ...interface{}) {
if l.enabled {
l.Logger.Fatalf(format, a...)
} else {
os.Exit(1)
}
}
// Fatalln is equivalent to l.Println() followed by a call to os.Exit(1).
func (l *Logger) Fatalln(a ...interface{}) {
if l.enabled {
l.Logger.Fatalln(a...)
} else {
os.Exit(1)
}
}
// Panic is equivalent to l.Print() followed by a call to panic().
func (l *Logger) Panic(a ...interface{}) {
if l.enabled {
l.Logger.Panic(a...)
}
}
// Panicf is equivalent to l.Printf() followed by a call to panic().
func (l *Logger) Panicf(format string, a ...interface{}) {
if l.enabled {
l.Logger.Panicf(format, a...)
}
}
// Panicln is equivalent to l.Println() followed by a call to panic().
func (l *Logger) Panicln(a ...interface{}) {
if l.enabled {
l.Logger.Panicln(a...)
}
}

2
middleware/README.md Normal file
View File

@ -0,0 +1,2 @@
# Middleware
Iris has its tiny middlewares here.

View File

@ -0,0 +1,157 @@
package basicauth
import (
"encoding/base64"
"strconv"
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/config"
)
type (
encodedUser struct {
HeaderValue string
Username string
logged bool
expires time.Time
}
encodedUsers []encodedUser
basicAuthMiddleware struct {
config config.BasicAuth
// 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.BasicAuth returns a HandlerFunc
// use: iris.UseFunc(New(...)), iris.Get(...,New(...),...)
func New(c config.BasicAuth) iris.HandlerFunc {
return NewHandler(c).Serve
}
// NewHandler takes one parameter, the config.BasicAuth returns a Handler
// use: iris.Use(NewHandler(...)), iris.Get(...,iris.HandlerFunc(NewHandler(...)),...)
func NewHandler(c config.BasicAuth) iris.Handler {
b := &basicAuthMiddleware{config: config.DefaultBasicAuth().MergeSingle(c)}
b.init()
return b
}
// Default takes one parameter, the users returns a HandlerFunc
// use: iris.UseFunc(Default(...)), iris.Get(...,Default(...),...)
func Default(users map[string]string) iris.HandlerFunc {
return DefaultHandler(users).Serve
}
// DefaultHandler takes one parameter, the users returns a Handler
// use: iris.Use(DefaultHandler(...)), iris.Get(...,iris.HandlerFunc(Default(...)),...)
func DefaultHandler(users map[string]string) iris.Handler {
c := config.DefaultBasicAuth()
c.Users = users
return NewHandler(c)
}
//
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: config.CookieExpireNever})
}
// 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 {
/* I spent time for nothing
if b.banEnabled && auth != nil { // this propably never work
if auth.tries == b.config.MaxTries {
auth.bannedTime = time.Now()
auth.unbanTime = time.Now().Add(b.config.BanDuration) // set the unban time
auth.tries++ // we plus them in order to check if already banned later
// client is banned send a forbidden status and don't continue
ctx.SetStatusCode(iris.StatusForbidden)
return
} else if auth.tries > b.config.MaxTries { // it's already banned, so check the ban duration with the bannedTime
if time.Now().After(auth.unbanTime) { // here we unban the client
auth.tries = 0
auth.bannedTime = config.CookieExpireNever
auth.unbanTime = config.CookieExpireNever
// continue and askCredentials as normal
} else {
// client is banned send a forbidden status and don't continue
ctx.SetStatusCode(iris.StatusForbidden)
return
}
}
}
if auth != nil {
auth.tries++
}*/
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().Before(auth.expires) {
b.askForCredentials(ctx) // ask for authentication again
return
}
}
//auth.tries = 0
ctx.Next() // continue
}
}

97
middleware/cors/README.md Normal file
View File

@ -0,0 +1,97 @@
## Middleware information
This is a fork of the CORS middleware from [here](https://github.com/rs/cors/)
## Description
It does some security work for you between the requests, a brief view on what you can set:
* AllowedOrigins []string
* AllowOriginFunc func(origin string) bool
* AllowedMethods []string
* AllowedHeadersAll bool
* ExposedHeaders []string
* AllowCredentials bool
* MaxAge int
* OptionsPassthrough bool
## Options
```go
// AllowedOrigins is a list of origins a cross-domain request can be executed from.
// If the special "*" value is present in the list, all origins will be allowed.
// An origin may contain a wildcard (*) to replace 0 or more characters
// (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penality.
// Only one wildcard can be used per origin.
// Default value is ["*"]
AllowedOrigins []string
// AllowOriginFunc is a custom function to validate the origin. It take the origin
// as argument and returns true if allowed or false otherwise. If this option is
// set, the content of AllowedOrigins is ignored.
AllowOriginFunc func(origin string) bool
// AllowedMethods is a list of methods the client is allowed to use with
// cross-domain requests. Default value is simple methods (GET and POST)
AllowedMethods []string
// AllowedHeaders is list of non simple headers the client is allowed to use with
// cross-domain requests.
// If the special "*" value is present in the list, all headers will be allowed.
// Default value is [] but "Origin" is always appended to the list.
AllowedHeaders []string
AllowedHeadersAll bool
// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
// API specification
ExposedHeaders []string
// AllowCredentials indicates whether the request can include user credentials like
// cookies, HTTP authentication or client side SSL certificates.
AllowCredentials bool
// MaxAge indicates how long (in seconds) the results of a preflight request
// can be cached
MaxAge int
// OptionsPassthrough instructs preflight to let other potential next handlers to
// process the OPTIONS method. Turn this on if your application handles OPTIONS.
OptionsPassthrough bool
// Debugging flag adds additional output to debug server side CORS issues
Debug bool
```
## How to use
```go
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/middleware/cors"
)
func main() {
//crs := cors.New(cors.Options{})
iris.Use(cors.Default()) // crs
iris.Get("/home", func(c *iris.Context) {
c.Write("Hello from /home")
})
println("Server is running at :8080")
iris.Listen(":8080")
}
```

403
middleware/cors/cors.go Normal file
View File

@ -0,0 +1,403 @@
// Cors credits goes to @rs
package cors
import (
"log"
"net/http"
"os"
"strconv"
"strings"
"github.com/kataras/iris"
)
const toLower = 'a' - 'A'
type converter func(string) string
type wildcard struct {
prefix string
suffix string
}
func (w wildcard) match(s string) bool {
return len(s) >= len(w.prefix+w.suffix) && strings.HasPrefix(s, w.prefix) && strings.HasSuffix(s, w.suffix)
}
// convert converts a list of string using the passed converter function
func convert(s []string, c converter) []string {
out := []string{}
for _, i := range s {
out = append(out, c(i))
}
return out
}
// parseHeaderList tokenize + normalize a string containing a list of headers
func parseHeaderList(headerList string) []string {
l := len(headerList)
h := make([]byte, 0, l)
upper := true
// Estimate the number headers in order to allocate the right splice size
t := 0
for i := 0; i < l; i++ {
if headerList[i] == ',' {
t++
}
}
headers := make([]string, 0, t)
for i := 0; i < l; i++ {
b := headerList[i]
if b >= 'a' && b <= 'z' {
if upper {
h = append(h, b-toLower)
} else {
h = append(h, b)
}
} else if b >= 'A' && b <= 'Z' {
if !upper {
h = append(h, b+toLower)
} else {
h = append(h, b)
}
} else if b == '-' || b == '_' || (b >= '0' && b <= '9') {
h = append(h, b)
}
if b == ' ' || b == ',' || i == l-1 {
if len(h) > 0 {
// Flush the found header
headers = append(headers, string(h))
h = h[:0]
upper = true
}
} else {
upper = b == '-' || b == '_'
}
}
return headers
}
// Options is a configuration container to setup the CORS middleware.
type Options struct {
// AllowedOrigins is a list of origins a cross-domain request can be executed from.
// If the special "*" value is present in the list, all origins will be allowed.
// An origin may contain a wildcard (*) to replace 0 or more characters
// (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penality.
// Only one wildcard can be used per origin.
// Default value is ["*"]
AllowedOrigins []string
// AllowOriginFunc is a custom function to validate the origin. It take the origin
// as argument and returns true if allowed or false otherwise. If this option is
// set, the content of AllowedOrigins is ignored.
AllowOriginFunc func(origin string) bool
// AllowedMethods is a list of methods the client is allowed to use with
// cross-domain requests. Default value is simple methods (GET and POST)
AllowedMethods []string
// AllowedHeaders is list of non simple headers the client is allowed to use with
// cross-domain requests.
// If the special "*" value is present in the list, all headers will be allowed.
// Default value is [] but "Origin" is always appended to the list.
AllowedHeaders []string
// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
// API specification
ExposedHeaders []string
// AllowCredentials indicates whether the request can include user credentials like
// cookies, HTTP authentication or client side SSL certificates.
AllowCredentials bool
// MaxAge indicates how long (in seconds) the results of a preflight request
// can be cached
MaxAge int
// OptionsPassthrough instructs preflight to let other potential next handlers to
// process the OPTIONS method. Turn this on if your application handles OPTIONS.
OptionsPassthrough bool
// Debugging flag adds additional output to debug server side CORS issues
Debug bool
}
// Cors http handler
type Cors struct {
// Debug logger
Log *log.Logger
// Set to true when allowed origins contains a "*"
allowedOriginsAll bool
// Normalized list of plain allowed origins
allowedOrigins []string
// List of allowed origins containing wildcards
allowedWOrigins []wildcard
// Optional origin validator function
allowOriginFunc func(origin string) bool
// Set to true when allowed headers contains a "*"
allowedHeadersAll bool
// Normalized list of allowed headers
allowedHeaders []string
// Normalized list of allowed methods
allowedMethods []string
// Normalized list of exposed headers
exposedHeaders []string
allowCredentials bool
maxAge int
optionPassthrough bool
}
// New creates a new Cors handler with the provided options.
func New(options Options) *Cors {
c := &Cors{
exposedHeaders: convert(options.ExposedHeaders, http.CanonicalHeaderKey),
allowOriginFunc: options.AllowOriginFunc,
allowCredentials: options.AllowCredentials,
maxAge: options.MaxAge,
optionPassthrough: options.OptionsPassthrough,
}
if options.Debug {
c.Log = log.New(os.Stdout, "[cors] ", log.LstdFlags)
}
// Normalize options
// Note: for origins and methods matching, the spec requires a case-sensitive matching.
// As it may error prone, we chose to ignore the spec here.
// Allowed Origins
if len(options.AllowedOrigins) == 0 {
// Default is all origins
c.allowedOriginsAll = true
} else {
c.allowedOrigins = []string{}
c.allowedWOrigins = []wildcard{}
for _, origin := range options.AllowedOrigins {
// Normalize
origin = strings.ToLower(origin)
if origin == "*" {
// If "*" is present in the list, turn the whole list into a match all
c.allowedOriginsAll = true
c.allowedOrigins = nil
c.allowedWOrigins = nil
break
} else if i := strings.IndexByte(origin, '*'); i >= 0 {
// Split the origin in two: start and end string without the *
w := wildcard{origin[0:i], origin[i+1 : len(origin)]}
c.allowedWOrigins = append(c.allowedWOrigins, w)
} else {
c.allowedOrigins = append(c.allowedOrigins, origin)
}
}
}
// Allowed Headers
if len(options.AllowedHeaders) == 0 {
// Use sensible defaults
c.allowedHeaders = []string{"Origin", "Accept", "Content-Type"}
} else {
// Origin is always appended as some browsers will always request for this header at preflight
c.allowedHeaders = convert(append(options.AllowedHeaders, "Origin"), http.CanonicalHeaderKey)
for _, h := range options.AllowedHeaders {
if h == "*" {
c.allowedHeadersAll = true
c.allowedHeaders = nil
break
}
}
}
// Allowed Methods
if len(options.AllowedMethods) == 0 {
// Default is spec's "simple" methods
c.allowedMethods = []string{"GET", "POST"}
} else {
c.allowedMethods = convert(options.AllowedMethods, strings.ToUpper)
}
return c
}
// Default creates a new Cors handler with default options
func Default() *Cors {
return New(Options{})
}
// DefaultCors creates a new Cors handler with default options
func DefaultCors() *Cors {
return Default()
}
func (c *Cors) Conflicts() string {
return "httpmethod"
}
func (c *Cors) Serve(ctx *iris.Context) {
if ctx.MethodString() == "OPTIONS" {
c.logf("Serve: Preflight request")
c.handlePreflight(ctx)
// Preflight requests are standalone and should stop the chain as some other
// middleware may not handle OPTIONS requests correctly. One typical example
// is authentication middleware ; OPTIONS requests won't carry authentication
// headers (see #1)
if c.optionPassthrough {
ctx.Next()
}
} else {
c.logf("Serve: Actual request")
c.handleActualRequest(ctx)
ctx.Next()
}
}
// handlePreflight handles pre-flight CORS requests
func (c *Cors) handlePreflight(ctx *iris.Context) {
origin := ctx.RequestHeader("Origin")
if ctx.MethodString() != "OPTIONS" {
c.logf(" Preflight aborted: %s!=OPTIONS", ctx.MethodString())
return
}
// Always set Vary headers
ctx.Response.Header.Add("Vary", "Origin")
ctx.Response.Header.Add("Vary", "Access-Control-Request-Method")
ctx.Response.Header.Add("Vary", "Access-Control-Request-Headers")
if origin == "" {
c.logf(" Preflight aborted: empty origin")
return
}
if !c.isOriginAllowed(origin) {
c.logf(" Preflight aborted: origin '%s' not allowed", origin)
return
}
reqMethod := ctx.RequestHeader("Access-Control-Request-Method")
if !c.isMethodAllowed(reqMethod) {
c.logf(" Preflight aborted: method '%s' not allowed", reqMethod)
return
}
reqHeaders := parseHeaderList(ctx.RequestHeader("Access-Control-Request-Headers"))
if !c.areHeadersAllowed(reqHeaders) {
c.logf(" Preflight aborted: headers '%v' not allowed", reqHeaders)
return
}
ctx.Response.Header.Set("Access-Control-Allow-Origin", origin)
// Spec says: Since the list of methods can be unbounded, simply returning the method indicated
// by Access-Control-Request-Method (if supported) can be enough
ctx.Response.Header.Set("Access-Control-Allow-Methods", strings.ToUpper(reqMethod))
if len(reqHeaders) > 0 {
// Spec says: Since the list of headers can be unbounded, simply returning supported headers
// from Access-Control-Request-Headers can be enough
ctx.Response.Header.Set("Access-Control-Allow-Headers", strings.Join(reqHeaders, ", "))
}
if c.allowCredentials {
ctx.Response.Header.Set("Access-Control-Allow-Credentials", "true")
}
if c.maxAge > 0 {
ctx.Response.Header.Set("Access-Control-Max-Age", strconv.Itoa(c.maxAge))
}
c.logf(" Preflight response headers: %v", ctx.Response.Header)
}
// handleActualRequest handles simple cross-origin requests, actual request or redirects
func (c *Cors) handleActualRequest(ctx *iris.Context) {
origin := ctx.RequestHeader("Origin")
if ctx.MethodString() == "OPTIONS" {
c.logf(" Actual request no headers added: method == %s", ctx.MethodString())
return
}
ctx.Response.Header.Add("Vary", "Origin")
if origin == "" {
c.logf(" Actual request no headers added: missing origin")
return
}
if !c.isOriginAllowed(origin) {
c.logf(" Actual request no headers added: origin '%s' not allowed", origin)
return
}
// Note that spec does define a way to specifically disallow a simple method like GET or
// POST. Access-Control-Allow-Methods is only used for pre-flight requests and the
// spec doesn't instruct to check the allowed methods for simple cross-origin requests.
// We think it's a nice feature to be able to have control on those methods though.
if !c.isMethodAllowed(ctx.MethodString()) {
c.logf(" Actual request no headers added: method '%s' not allowed", ctx.MethodString())
return
}
ctx.Response.Header.Set("Access-Control-Allow-Origin", origin)
if len(c.exposedHeaders) > 0 {
ctx.Response.Header.Set("Access-Control-Expose-Headers", strings.Join(c.exposedHeaders, ", "))
}
if c.allowCredentials {
ctx.Response.Header.Set("Access-Control-Allow-Credentials", "true")
}
c.logf(" Actual response added headers: %v", ctx.Response.Header)
}
// convenience method. checks if debugging is turned on before printing
func (c *Cors) logf(format string, a ...interface{}) {
if c.Log != nil {
c.Log.Printf(format, a...)
}
}
// isOriginAllowed checks if a given origin is allowed to perform cross-domain requests
// on the endpoint
func (c *Cors) isOriginAllowed(origin string) bool {
if c.allowOriginFunc != nil {
return c.allowOriginFunc(origin)
}
if c.allowedOriginsAll {
return true
}
origin = strings.ToLower(origin)
for _, o := range c.allowedOrigins {
if o == origin {
return true
}
}
for _, w := range c.allowedWOrigins {
if w.match(origin) {
return true
}
}
return false
}
// isMethodAllowed checks if a given method can be used as part of a cross-domain request
// on the endpoing
func (c *Cors) isMethodAllowed(method string) bool {
if len(c.allowedMethods) == 0 {
// If no method allowed, always return false, even for preflight request
return false
}
method = strings.ToUpper(method)
if method == "OPTIONS" {
// Always allow preflight requests
return true
}
for _, m := range c.allowedMethods {
if m == method {
return true
}
}
return false
}
// areHeadersAllowed checks if a given list of headers are allowed to used within
// a cross-domain request.
func (c *Cors) areHeadersAllowed(requestedHeaders []string) bool {
if c.allowedHeadersAll || len(requestedHeaders) == 0 {
return true
}
for _, header := range requestedHeaders {
header = http.CanonicalHeaderKey(header)
found := false
for _, h := range c.allowedHeaders {
if h == header {
found = true
}
}
if !found {
return false
}
}
return true
}

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

@ -0,0 +1,66 @@
## 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)
## 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 = Ãåéá, %s
```
```go
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/middleware/i18n"
)
func main() {
iris.UseFunc(i18n.I18n(i18n.Options{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)
})
println("Server is running at :8080")
iris.Listen(":8080")
}
```
### [For a working example, click here](https://github.com/kataras/iris/tree/examples/middleware_internationalization_i18n)

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

@ -0,0 +1,99 @@
package i18n
import (
"strings"
"github.com/Unknwon/i18n"
"github.com/kataras/iris"
)
// AcceptLanguage is the Header key "Accept-Language"
const AcceptLanguage = "Accept-Language"
// Options the i18n options
type Options 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
}
type i18nMiddleware struct {
options Options
}
func (i *i18nMiddleware) Serve(ctx *iris.Context) {
wasByCookie := false
// try to get by url parameter
language := ctx.URLParam(i.options.URLParameter)
if language == "" {
// then try to take the lang field from the cookie
language = ctx.GetCookie("lang")
if len(language) > 0 {
wasByCookie = true
} else {
// try to get by the request headers(?)
if langHeader := ctx.RequestHeader(AcceptLanguage); 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("language", language)
}
if language == "" {
language = i.options.Default
}
locale := i18n.Locale{language}
ctx.Set("language", language)
ctx.Set("translate", locale.Tr)
ctx.Next()
}
// I18nHandler returns the middleware which is just an iris.handler
func I18nHandler(_options ...Options) *i18nMiddleware {
i := &i18nMiddleware{}
if len(_options) == 0 || (len(_options) > 0 && len(_options[0].Languages) == 0) {
panic("You cannot use this middleware without set the Languages option, please try again and read the docs.")
}
i.options = _options[0]
firstlanguage := ""
//load the files
for k, v := range i.options.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 i.options.Default == "" {
i.options.Default = firstlanguage
}
i18n.SetDefaultLang(i.options.Default)
return i
}
// I18n returns the middleware as iris.HandlerFunc with the passed options
func I18n(_options ...Options) iris.HandlerFunc {
return I18nHandler(_options...).Serve
}

View File

@ -0,0 +1,64 @@
## Middleware information
This folder contains a middleware for the build'n Iris logger but for the requests.
## How to use
```go
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/middleware/logger"
)
func main() {
iris.UseFunc(logger.Default())
// or iris.Use(logger.DefaultHandler())
// or iris.UseFunc(iris.HandlerFunc(logger.DefaultHandler())
// or iris.Get("/", logger.Default(), func (ctx *iris.Context){})
// or iris.Get("/", iris.HandlerFunc(logger.DefaultHandler()), func (ctx *iris.Context){})
// Custom settings:
// ...
// iris.UseFunc(logger.Custom(writer io.Writer, prefix string, flag int))
// and so on...
// Custom options:
// ...
// iris.UseFunc(logger.Default(logger.Options{IP:false})) // don't log the ip
// or iris.UseFunc(logger.Custom(writer io.Writer, prefix string, flag int, logger.Options{IP:false}))
// and so on...
iris.Get("/", func(ctx *iris.Context) {
ctx.Write("hello")
})
iris.Get("/1", func(ctx *iris.Context) {
ctx.Write("hello")
})
iris.Get("/3", func(ctx *iris.Context) {
ctx.Write("hello")
})
// IF YOU WANT LOGGER TO LOGS THE HTTP ERRORS ALSO THEN:
// FUTURE: iris.OnError(404, logger.Default(logger.Options{Latency: false}))
// NOW:
errorLogger := logger.Default(logger.Options{Latency: false}) //here we just disable to log the latency, no need for error pages I think
// yes we have options look at the logger.Options inside middleware/logger.go
iris.OnError(404, func(ctx *iris.Context) {
errorLogger.Serve(ctx)
ctx.Write("My Custom 404 error page ")
})
//
println("Server is running at :80")
iris.Listen(":80")
}
```

122
middleware/logger/logger.go Normal file
View File

@ -0,0 +1,122 @@
package logger
import (
"github.com/kataras/iris"
"github.com/kataras/iris/config"
"github.com/kataras/iris/logger"
"strconv"
"time"
)
// Options are the options of the logger middlweare
// contains 5 bools
// Latency, Status, IP, Method, Path
// if set to true then these will print
type Options struct {
Latency bool
Status bool
IP bool
Method bool
Path bool
}
// DefaultOptions returns an options which all properties are true
func DefaultOptions() Options {
return Options{true, true, true, true, true}
}
type loggerMiddleware struct {
*logger.Logger
options Options
}
// a poor and ugly implementation of a logger but no need to worry about this at the moment
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.PathString()
method = ctx.MethodString()
if l.options.Latency {
startTime = time.Now()
}
ctx.Next()
if l.options.Latency {
//no time.Since in order to format it well after
endTime = time.Now()
date = endTime.Format("01/02 - 15:04:05")
latency = endTime.Sub(startTime)
}
if l.options.Status {
status = strconv.Itoa(ctx.Response.StatusCode())
}
if l.options.IP {
ip = ctx.RemoteAddr()
}
if !l.options.Method {
method = ""
}
if !l.options.Path {
path = ""
}
//finally print the logs
if l.options.Latency {
l.Printf("%s %v %4v %s %s %s", date, status, latency, ip, method, path)
} else {
l.Printf("%s %v %s %s %s", date, status, ip, method, path)
}
}
func newLoggerMiddleware(loggerCfg config.Logger, options ...Options) *loggerMiddleware {
loggerCfg = config.DefaultLogger().MergeSingle(loggerCfg)
l := &loggerMiddleware{Logger: logger.New(loggerCfg)}
if len(options) > 0 {
l.options = options[0]
} else {
l.options = DefaultOptions()
}
return l
}
//all bellow are just for flexibility
// DefaultHandler returns the logger middleware with the default settings
func DefaultHandler(options ...Options) iris.Handler {
loggerCfg := config.DefaultLogger()
return newLoggerMiddleware(loggerCfg, options...)
}
// Default returns the logger middleware as HandlerFunc with the default settings
func Default(options ...Options) iris.HandlerFunc {
return DefaultHandler(options...).Serve
}
// CustomHandler returns the logger middleware with customized settings
// accepts 3 parameters
// first parameter is the writer (io.Writer)
// second parameter is the prefix of which the message will follow up
// third parameter is the logger.Options
func CustomHandler(loggerCfg config.Logger, options ...Options) iris.Handler {
return newLoggerMiddleware(loggerCfg, options...)
}
// Custom returns the logger middleware as HandlerFunc with customized settings
// accepts 3 parameters
// first parameter is the writer (io.Writer)
// second parameter is the prefix of which the message will follow up
// third parameter is the logger.Options
func Custom(loggerCfg config.Logger, options ...Options) iris.HandlerFunc {
return CustomHandler(loggerCfg, options...).Serve
}

View File

@ -0,0 +1,30 @@
## Middleware information
This folder contains a middleware for safety recover the server from panic
## How to use
```go
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/middleware/recovery"
"os"
)
func main() {
iris.Use(recovery.New(os.Stderr)) // optional parameter is the writer which the stack of the panic will be printed
iris.Get("/", func(ctx *iris.Context) {
ctx.Write("Hi, let's panic")
panic("errorrrrrrrrrrrrrrr")
})
println("Server is running at :8080")
iris.Listen(":8080")
}
```

View File

@ -0,0 +1,45 @@
package recovery
import (
"io"
"os"
"time"
"github.com/kataras/iris"
)
type recovery struct {
//out optional output to log any panics
out io.Writer
}
func (r recovery) Serve(ctx *iris.Context) {
defer func() {
if err := recover(); err != nil {
r.out.Write([]byte("[" + time.Now().String() + "]Recovery from panic \n"))
//ctx.Panic just sends http status 500 by default, but you can change it by: iris.OnPanic(func( c *iris.Context){})
ctx.Panic()
}
}()
ctx.Next()
}
// Recovery restores the server on internal server errors (panics)
// receives an optional writer, the default is the os.Stderr if no out writer given
// returns the middleware as iris.Handler
// same as New(...)
func Recovery(out ...io.Writer) iris.Handler {
r := recovery{os.Stderr}
if out != nil && len(out) == 1 {
r.out = out[0]
}
return r
}
// New restores the server on internal server errors (panics)
// receives an optional writer, the default is the os.Stderr if no out writer given
// returns the middleware as iris.Handler
// same as Recovery(...)
func New(out ...io.Writer) iris.Handler {
return Recovery(out...)
}

View File

@ -0,0 +1,69 @@
## Middleware information
This was out-of-the-box iris supported before, but after Iris V1.1.0 it's not, so I had to modify it.
This folder contains a middleware ported to Iris from a third-party middleware named secure.
More can be found here:
[https://github.com/unrolled/secure](https://github.com/unrolled/secure)
## Description
Secure is an HTTP middleware for Go that facilitates some quick security wins.
## How to use
```go
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/middleware/secure"
)
func main() {
s := secure.New(secure.Options{
AllowedHosts: []string{"ssl.example.com"}, // AllowedHosts is a list of fully qualified domain names that are allowed. Default is empty list, which allows any and all host names.
SSLRedirect: true, // If SSLRedirect is set to true, then only allow HTTPS requests. Default is false.
SSLTemporaryRedirect: false, // If SSLTemporaryRedirect is true, the a 302 will be used while redirecting. Default is false (301).
SSLHost: "ssl.example.com", // SSLHost is the host name that is used to redirect HTTP requests to HTTPS. Default is "", which indicates to use the same host.
SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"}, // SSLProxyHeaders is set of header keys with associated values that would indicate a valid HTTPS request. Useful when using Nginx: `map[string]string{"X-Forwarded-Proto": "https"}`. Default is blank map.
STSSeconds: 315360000, // STSSeconds is the max-age of the Strict-Transport-Security header. Default is 0, which would NOT include the header.
STSIncludeSubdomains: true, // If STSIncludeSubdomains is set to true, the `includeSubdomains` will be appended to the Strict-Transport-Security header. Default is false.
STSPreload: true, // If STSPreload is set to true, the `preload` flag will be appended to the Strict-Transport-Security header. Default is false.
ForceSTSHeader: false, // STS header is only included when the connection is HTTPS. If you want to force it to always be added, set to true. `IsDevelopment` still overrides this. Default is false.
FrameDeny: true, // If FrameDeny is set to true, adds the X-Frame-Options header with the value of `DENY`. Default is false.
CustomFrameOptionsValue: "SAMEORIGIN", // CustomFrameOptionsValue allows the X-Frame-Options header value to be set with a custom value. This overrides the FrameDeny option.
ContentTypeNosniff: true, // If ContentTypeNosniff is true, adds the X-Content-Type-Options header with the value `nosniff`. Default is false.
BrowserXSSFilter: true, // If BrowserXssFilter is true, adds the X-XSS-Protection header with the value `1; mode=block`. Default is false.
ContentSecurityPolicy: "default-src 'self'", // ContentSecurityPolicy allows the Content-Security-Policy header value to be set with a custom value. Default is "".
PublicKey: `pin-sha256="base64+primary=="; pin-sha256="base64+backup=="; max-age=5184000; includeSubdomains; report-uri="https://www.example.com/hpkp-report"`, // PublicKey implements HPKP to prevent MITM attacks with forged certificates. Default is "".
IsDevelopment: true, // This will cause the AllowedHosts, SSLRedirect, and STSSeconds/STSIncludeSubdomains options to be ignored during development. When deploying to production, be sure to set this to false.
})
iris.UseFunc(func(c *iris.Context) {
err := s.Process(c)
// If there was an error, do not continue.
if err != nil {
return
}
c.Next()
})
iris.Get("/home", func(c *iris.Context) {
c.Write("Hello from /home")
})
println("Server is running at :8080")
iris.Listen(":8080")
}
```

205
middleware/secure/secure.go Normal file
View File

@ -0,0 +1,205 @@
/*
This has been modified to work with Iris, credits goes to https://github.com/unrolled/secure
*/
package secure
import (
"fmt"
"strings"
"github.com/kataras/iris"
)
const (
stsHeader = "Strict-Transport-Security"
stsSubdomainString = "; includeSubdomains"
stsPreloadString = "; preload"
frameOptionsHeader = "X-Frame-Options"
frameOptionsValue = "DENY"
contentTypeHeader = "X-Content-Type-Options"
contentTypeValue = "nosniff"
xssProtectionHeader = "X-XSS-Protection"
xssProtectionValue = "1; mode=block"
cspHeader = "Content-Security-Policy"
hpkpHeader = "Public-Key-Pins"
)
func defaultBadHostHandler(ctx *iris.Context) {
ctx.Text(iris.StatusInternalServerError, "Bad Host")
}
// Options is a struct for specifying configuration options for the secure.Secure middleware.
type Options struct {
// AllowedHosts is a list of fully qualified domain names that are allowed. Default is empty list, which allows any and all host names.
AllowedHosts []string
// If SSLRedirect is set to true, then only allow https requests. Default is false.
SSLRedirect bool
// If SSLTemporaryRedirect is true, the a 302 will be used while redirecting. Default is false (301).
SSLTemporaryRedirect bool
// SSLHost is the host name that is used to redirect http requests to https. Default is "", which indicates to use the same host.
SSLHost string
// SSLProxyHeaders is set of header keys with associated values that would indicate a valid https request. Useful when using Nginx: `map[string]string{"X-Forwarded-Proto": "https"}`. Default is blank map.
SSLProxyHeaders map[string]string
// STSSeconds is the max-age of the Strict-Transport-Security header. Default is 0, which would NOT include the header.
STSSeconds int64
// If STSIncludeSubdomains is set to true, the `includeSubdomains` will be appended to the Strict-Transport-Security header. Default is false.
STSIncludeSubdomains bool
// If STSPreload is set to true, the `preload` flag will be appended to the Strict-Transport-Security header. Default is false.
STSPreload bool
// If ForceSTSHeader is set to true, the STS header will be added even when the connection is HTTP. Default is false.
ForceSTSHeader bool
// If FrameDeny is set to true, adds the X-Frame-Options header with the value of `DENY`. Default is false.
FrameDeny bool
// CustomFrameOptionsValue allows the X-Frame-Options header value to be set with a custom value. This overrides the FrameDeny option.
CustomFrameOptionsValue string
// If ContentTypeNosniff is true, adds the X-Content-Type-Options header with the value `nosniff`. Default is false.
ContentTypeNosniff bool
// BrowserXSSFilter If it's true, adds the X-XSS-Protection header with the value `1; mode=block`. Default is false.
BrowserXSSFilter bool
// ContentSecurityPolicy allows the Content-Security-Policy header value to be set with a custom value. Default is "".
ContentSecurityPolicy string
// PublicKey implements HPKP to prevent MITM attacks with forged certificates. Default is "".
PublicKey string
// When developing, the AllowedHosts, SSL, and STS options can cause some unwanted effects. Usually testing happens on http, not https, and on localhost, not your production domain... so set this to true for dev environment.
// If you would like your development environment to mimic production with complete Host blocking, SSL redirects, and STS headers, leave this as false. Default if false.
IsDevelopment bool
}
// Secure is a middleware that helps setup a few basic security features. A single secure.Options struct can be
// provided to configure which features should be enabled, and the ability to override a few of the default values.
type Secure struct {
// Customize Secure with an Options struct.
opt Options
// Handlers for when an error occurs (ie bad host).
badHostHandler iris.Handler
}
// New constructs a new Secure instance with supplied options.
func New(options ...Options) *Secure {
var o Options
if len(options) == 0 {
o = Options{}
} else {
o = options[0]
}
return &Secure{
opt: o,
badHostHandler: iris.HandlerFunc(defaultBadHostHandler),
}
}
// SetBadHostHandler sets the handler to call when secure rejects the host name.
func (s *Secure) SetBadHostHandler(handler iris.Handler) {
s.badHostHandler = handler
}
// Handler implements the iris.HandlerFunc for integration with iris.
func (s *Secure) Handler(h iris.Handler) iris.Handler {
return iris.HandlerFunc(func(ctx *iris.Context) {
// Let secure process the request. If it returns an error,
// that indicates the request should not continue.
err := s.Process(ctx)
// If there was an error, do not continue.
if err != nil {
return
}
h.Serve(ctx)
})
}
// Process runs the actual checks and returns an error if the middleware chain should stop.
func (s *Secure) Process(ctx *iris.Context) error {
// Allowed hosts check.
if len(s.opt.AllowedHosts) > 0 && !s.opt.IsDevelopment {
isGoodHost := false
for _, allowedHost := range s.opt.AllowedHosts {
if strings.EqualFold(allowedHost, string(ctx.Host())) {
isGoodHost = true
break
}
}
if !isGoodHost {
s.badHostHandler.Serve(ctx)
return fmt.Errorf("Bad host name: %s", string(ctx.Host()))
}
}
// Determine if we are on HTTPS.
isSSL := strings.EqualFold(string(ctx.Request.URI().Scheme()), "https") || ctx.IsTLS()
if !isSSL {
for k, v := range s.opt.SSLProxyHeaders {
if ctx.RequestHeader(k) == v {
isSSL = true
break
}
}
}
// SSL check.
if s.opt.SSLRedirect && !isSSL && !s.opt.IsDevelopment {
url := ctx.Request.URI()
url.SetScheme("https")
url.SetHostBytes(ctx.Host())
if len(s.opt.SSLHost) > 0 {
url.SetHost(s.opt.SSLHost)
}
status := iris.StatusMovedPermanently
if s.opt.SSLTemporaryRedirect {
status = iris.StatusTemporaryRedirect
}
ctx.Redirect(url.String(), status)
return fmt.Errorf("Redirecting to HTTPS")
}
// Strict Transport Security header. Only add header when we know it's an SSL connection.
// See https://tools.ietf.org/html/rfc6797#section-7.2 for details.
if s.opt.STSSeconds != 0 && (isSSL || s.opt.ForceSTSHeader) && !s.opt.IsDevelopment {
stsSub := ""
if s.opt.STSIncludeSubdomains {
stsSub = stsSubdomainString
}
if s.opt.STSPreload {
stsSub += stsPreloadString
}
ctx.Response.Header.Add(stsHeader, fmt.Sprintf("max-age=%d%s", s.opt.STSSeconds, stsSub))
}
// Frame Options header.
if len(s.opt.CustomFrameOptionsValue) > 0 {
ctx.Response.Header.Add(frameOptionsHeader, s.opt.CustomFrameOptionsValue)
} else if s.opt.FrameDeny {
ctx.Response.Header.Add(frameOptionsHeader, frameOptionsValue)
}
// Content Type Options header.
if s.opt.ContentTypeNosniff {
ctx.Response.Header.Add(contentTypeHeader, contentTypeValue)
}
// XSS Protection header.
if s.opt.BrowserXSSFilter {
ctx.Response.Header.Add(xssProtectionHeader, xssProtectionValue)
}
// HPKP header.
if len(s.opt.PublicKey) > 0 && isSSL && !s.opt.IsDevelopment {
ctx.Response.Header.Add(hpkpHeader, s.opt.PublicKey)
}
// Content Security Policy header.
if len(s.opt.ContentSecurityPolicy) > 0 {
ctx.Response.Header.Add(cspHeader, s.opt.ContentSecurityPolicy)
}
return nil
}

123
npm/npm.go Normal file
View File

@ -0,0 +1,123 @@
package npm
import (
"fmt"
"strings"
"time"
"github.com/kataras/iris/utils"
)
var (
// NodeModules is the path of the root npm modules
// Ex: C:\\Users\\kataras\\AppData\\Roaming\\npm\\node_modules
NodeModules string
)
type (
// Result holds Message and Error, if error != nil then the npm command has failed
Result struct {
// Message the message (string)
Message string
// Error the error (if any)
Error error
}
)
// init sets the root directory for the node_modules
func init() {
NodeModules = utils.MustCommand("npm", "root", "-g") //here it ends with \n we have to remove it
NodeModules = NodeModules[0 : len(NodeModules)-1]
}
func success(output string, a ...interface{}) Result {
return Result{fmt.Sprintf(output, a...), nil}
}
func fail(errMsg string, a ...interface{}) Result {
return Result{"", fmt.Errorf("\n"+errMsg, a...)}
}
// Output returns the error message if result.Error exists, otherwise returns the result.Message
func (res Result) Output() (out string) {
if res.Error != nil {
out = res.Error.Error()
} else {
out = res.Message
}
return
}
// Install installs a module
func Install(moduleName string) Result {
finish := make(chan bool)
go func() {
print("\n|")
print("_")
print("|")
for {
select {
case v := <-finish:
{
if v {
print("\010\010\010") //remove the loading chars
close(finish)
return
}
}
default:
print("\010\010-")
time.Sleep(time.Second / 2)
print("\010\\")
time.Sleep(time.Second / 2)
print("\010|")
time.Sleep(time.Second / 2)
print("\010/")
time.Sleep(time.Second / 2)
print("\010-")
time.Sleep(time.Second / 2)
print("|")
}
}
}()
out, err := utils.Command("npm", "install", moduleName, "-g")
finish <- true
if err != nil {
return fail("Error installing module %s. Trace: %s", moduleName, err.Error())
}
return success("\n%s installed %s", moduleName, out)
}
// Unistall removes a module
func Unistall(moduleName string) Result {
out, err := utils.Command("npm", "unistall", "-g", moduleName)
if err != nil {
return fail("Error unstalling module %s. Trace: %s", moduleName, err.Error())
}
return success("\n %s unistalled %s", moduleName, out)
}
// Abs returns the absolute path of the global node_modules directory + relative
func Abs(relativePath string) string {
return NodeModules + utils.PathSeparator + strings.Replace(relativePath, "/", utils.PathSeparator, -1)
}
// Exists returns true if a module exists
// here we have two options
//1 . search by command something like npm -ls -g --depth=x
//2. search on files, we choose the second
func Exists(executableRelativePath string) bool {
execAbsPath := Abs(executableRelativePath)
if execAbsPath == "" {
return false
}
return utils.Exists(execAbsPath)
}

674
party.go Normal file
View File

@ -0,0 +1,674 @@
package iris
import (
"path"
"reflect"
"strconv"
"strings"
"os"
"time"
"github.com/kataras/iris/config"
"github.com/kataras/iris/context"
"github.com/kataras/iris/utils"
"github.com/valyala/fasthttp"
)
type (
// IParty is the interface which implements the whole Party of routes
IParty interface {
Handle(string, string, ...Handler)
HandleFunc(string, string, ...HandlerFunc)
HandleAnnotated(Handler) error
API(path string, controller HandlerAPI, middlewares ...HandlerFunc) error
Get(string, ...HandlerFunc)
Post(string, ...HandlerFunc)
Put(string, ...HandlerFunc)
Delete(string, ...HandlerFunc)
Connect(string, ...HandlerFunc)
Head(string, ...HandlerFunc)
Options(string, ...HandlerFunc)
Patch(string, ...HandlerFunc)
Trace(string, ...HandlerFunc)
Any(string, ...HandlerFunc)
Use(...Handler)
UseFunc(...HandlerFunc)
StaticHandlerFunc(systemPath string, stripSlashes int, compress bool, generateIndexPages bool, indexNames []string) HandlerFunc
Static(string, string, int)
StaticFS(string, string, int)
StaticWeb(relative string, systemPath string, stripSlashes int)
StaticServe(systemPath string, requestPath ...string)
Party(string, ...HandlerFunc) IParty // Each party can have a party too
IsRoot() bool
}
// GardenParty is the struct which makes all the job for registering routes and middlewares
GardenParty struct {
relativePath string
station *Iris // this station is where the party is happening, this station's Garden is the same for all Parties per Station & Router instance
middleware Middleware
root bool
}
)
var _ IParty = &GardenParty{}
// IsRoot returns true if this is the root party ("/")
func (p *GardenParty) IsRoot() bool {
return p.root
}
// Handle registers a route to the server's router
// if empty method is passed then registers handler(s) for all methods, same as .Any
func (p *GardenParty) Handle(method string, registedPath string, handlers ...Handler) {
if method == "" { // then use like it was .Any
for _, k := range AllMethods {
p.Handle(k, registedPath, handlers...)
}
return
}
path := fixPath(p.relativePath + registedPath) // keep the last "/" as default ex: "/xyz/"
if !p.station.config.DisablePathCorrection {
// if we have path correction remove it with absPath
path = fixPath(absPath(p.relativePath, registedPath)) // "/xyz"
}
middleware := JoinMiddleware(p.middleware, handlers)
route := NewRoute(method, path, middleware)
p.station.plugins.DoPreHandle(route)
p.station.addRoute(route)
p.station.plugins.DoPostHandle(route)
}
// HandleFunc registers and returns a route with a method string, path string and a handler
// registedPath is the relative url path
// handler is the iris.Handler which you can pass anything you want via iris.ToHandlerFunc(func(res,req){})... or just use func(c *iris.Context)
func (p *GardenParty) HandleFunc(method string, registedPath string, handlersFn ...HandlerFunc) {
p.Handle(method, registedPath, ConvertToHandlers(handlersFn)...)
}
// HandleAnnotated registers a route handler using a Struct implements iris.Handler (as anonymous property)
// which it's metadata has the form of
// `method:"path"` and returns the route and an error if any occurs
// handler is passed by func(urstruct MyStruct) Serve(ctx *Context) {}
func (p *GardenParty) HandleAnnotated(irisHandler Handler) error {
var method string
var path string
var errMessage = ""
val := reflect.ValueOf(irisHandler).Elem()
for i := 0; i < val.NumField(); i++ {
typeField := val.Type().Field(i)
if typeField.Anonymous && typeField.Name == "Handler" {
tags := strings.Split(strings.TrimSpace(string(typeField.Tag)), " ")
firstTag := tags[0]
idx := strings.Index(string(firstTag), ":")
tagName := strings.ToUpper(string(firstTag[:idx]))
tagValue, unqerr := strconv.Unquote(string(firstTag[idx+1:]))
if unqerr != nil {
errMessage = errMessage + "\non getting path: " + unqerr.Error()
continue
}
path = tagValue
avalaibleMethodsStr := strings.Join(AllMethods[0:], ",")
if !strings.Contains(avalaibleMethodsStr, tagName) {
//wrong method passed
errMessage = errMessage + "\nWrong method passed to the anonymous property iris.Handler -> " + tagName
continue
}
method = tagName
} else {
errMessage = "\nStruct passed but it doesn't have an anonymous property of type iris.Hanndler, please refer to docs\n"
}
}
if errMessage == "" {
p.Handle(method, path, irisHandler)
}
var err error
if errMessage != "" {
err = ErrHandleAnnotated.Format(errMessage)
}
return err
}
// API converts & registers a custom struct to the router
// receives two parameters
// first is the request path (string)
// second is the custom struct (interface{}) which can be anything that has a *iris.Context as field.
// third are the common middlewares, is optional parameter
//
// Recommend to use when you retrieve data from an external database,
// and the router-performance is not the (only) thing which slows the server's overall performance.
//
// This is a slow method, if you care about router-performance use the Handle/HandleFunc/Get/Post/Put/Delete/Trace/Options... instead
//
// Usage:
// All the below methods are optional except the *iris.Context field,
// example with /users :
/*
package main
import (
"github.com/kataras/iris"
)
type UserAPI struct {
*iris.Context
}
// GET /users
func (u UserAPI) Get() {
u.Write("Get from /users")
// u.JSON(iris.StatusOK,myDb.AllUsers())
}
// GET /:param1 which its value passed to the id argument
func (u UserAPI) GetBy(id string) { // id equals to u.Param("param1")
u.Write("Get from /users/%s", id)
// u.JSON(iris.StatusOK, myDb.GetUserById(id))
}
// PUT /users
func (u UserAPI) Put() {
name := u.FormValue("name")
// myDb.InsertUser(...)
println(string(name))
println("Put from /users")
}
// POST /users/:param1
func (u UserAPI) PostBy(id string) {
name := u.FormValue("name") // you can still use the whole Context's features!
// myDb.UpdateUser(...)
println(string(name))
println("Post from /users/" + id)
}
// DELETE /users/:param1
func (u UserAPI) DeleteBy(id string) {
// myDb.DeleteUser(id)
println("Delete from /" + id)
}
func main() {
iris.API("/users", UserAPI{})
iris.Listen(":80")
}
*/
func (p *GardenParty) API(path string, controller HandlerAPI, middlewares ...HandlerFunc) error {
// here we need to find the registed methods and convert them to handler funcs
// methods are collected by method naming: Get(),GetBy(...), Post(),PostBy(...), Put() and so on
typ := reflect.ValueOf(controller).Type()
contextField, found := typ.FieldByName("Context")
if !found {
return ErrControllerContextNotFound.Return()
}
// check & register the Get(),Post(),Put(),Delete() and so on
for _, methodName := range AllMethods {
methodCapitalName := strings.Title(strings.ToLower(methodName))
if method, found := typ.MethodByName(methodCapitalName); found {
methodFunc := method.Func
if !methodFunc.IsValid() || methodFunc.Type().NumIn() > 1 { // for any case
continue
}
func(path string, typ reflect.Type, contextField reflect.StructField, methodFunc reflect.Value, method string) {
handlersFn := make([]HandlerFunc, 0)
handlersFn = append(handlersFn, middlewares...)
handlersFn = append(handlersFn, func(ctx *Context) {
newController := reflect.New(typ).Elem()
newController.FieldByName("Context").Set(reflect.ValueOf(ctx))
methodFunc.Call([]reflect.Value{newController})
})
// register route
p.HandleFunc(method, path, handlersFn...)
}(path, typ, contextField, methodFunc, methodName)
}
}
// check for GetBy/PostBy(id string, something_else string) , these must be requested by the same order.
// (we could do this in the same top loop but I don't want)
// GET, DELETE -> with url named parameters (/users/:id/:secondArgumentIfExists)
// POST, PUT -> with post values (form)
// all other with URL Parameters (?something=this&else=other
//
// or no, I changed my mind, let all be named parameters and let users to decide what info they need,
// using the Context to take more values (post form,url params and so on).-
for _, methodName := range AllMethods {
methodWithBy := strings.Title(strings.ToLower(methodName)) + "By"
if method, found := typ.MethodByName(methodWithBy); found {
methodFunc := method.Func
if !methodFunc.IsValid() || methodFunc.Type().NumIn() < 2 { //it's By but it has not receive any arguments so its not api's
continue
}
methodFuncType := methodFunc.Type()
numInLen := methodFuncType.NumIn() // how much data we should receive from the request
registedPath := path
for i := 1; i < numInLen; i++ { // from 1 because the first is the 'object'
if registedPath[len(registedPath)-1] == SlashByte {
registedPath += ":param" + strconv.Itoa(i)
} else {
registedPath += "/:param" + strconv.Itoa(i)
}
}
func(registedPath string, typ reflect.Type, contextField reflect.StructField, methodFunc reflect.Value, paramsLen int, method string) {
handlersFn := make([]HandlerFunc, 0)
handlersFn = append(handlersFn, middlewares...)
handlersFn = append(handlersFn, func(ctx *Context) {
newController := reflect.New(typ).Elem()
newController.FieldByName("Context").Set(reflect.ValueOf(ctx))
args := make([]reflect.Value, paramsLen+1, paramsLen+1)
args[0] = newController
for i := 0; i < paramsLen; i++ {
args[i+1] = reflect.ValueOf(ctx.Params[i].Value)
}
methodFunc.Call(args)
})
// register route
p.HandleFunc(method, registedPath, handlersFn...)
}(registedPath, typ, contextField, methodFunc, numInLen-1, methodName)
}
}
return nil
}
// Get registers a route for the Get http method
func (p *GardenParty) Get(path string, handlersFn ...HandlerFunc) {
p.HandleFunc(MethodGet, path, handlersFn...)
}
// Post registers a route for the Post http method
func (p *GardenParty) Post(path string, handlersFn ...HandlerFunc) {
p.HandleFunc(MethodPost, path, handlersFn...)
}
// Put registers a route for the Put http method
func (p *GardenParty) Put(path string, handlersFn ...HandlerFunc) {
p.HandleFunc(MethodPut, path, handlersFn...)
}
// Delete registers a route for the Delete http method
func (p *GardenParty) Delete(path string, handlersFn ...HandlerFunc) {
p.HandleFunc(MethodDelete, path, handlersFn...)
}
// Connect registers a route for the Connect http method
func (p *GardenParty) Connect(path string, handlersFn ...HandlerFunc) {
p.HandleFunc(MethodConnect, path, handlersFn...)
}
// Head registers a route for the Head http method
func (p *GardenParty) Head(path string, handlersFn ...HandlerFunc) {
p.HandleFunc(MethodHead, path, handlersFn...)
}
// Options registers a route for the Options http method
func (p *GardenParty) Options(path string, handlersFn ...HandlerFunc) {
p.HandleFunc(MethodOptions, path, handlersFn...)
}
// Patch registers a route for the Patch http method
func (p *GardenParty) Patch(path string, handlersFn ...HandlerFunc) {
p.HandleFunc(MethodPatch, path, handlersFn...)
}
// Trace registers a route for the Trace http method
func (p *GardenParty) Trace(path string, handlersFn ...HandlerFunc) {
p.HandleFunc(MethodTrace, path, handlersFn...)
}
// Any registers a route for ALL of the http methods (Get,Post,Put,Head,Patch,Options,Connect,Delete)
func (p *GardenParty) Any(registedPath string, handlersFn ...HandlerFunc) {
for _, k := range AllMethods {
p.HandleFunc(k, registedPath, handlersFn...)
}
}
// H_ is used to convert a context.IContext handler func to iris.HandlerFunc, is used only inside iris internal package to avoid import cycles
func (p *GardenParty) H_(method string, registedPath string, fn func(context.IContext)) {
p.HandleFunc(method, registedPath, func(ctx *Context) {
fn(ctx)
})
}
// Use registers a Handler middleware
func (p *GardenParty) Use(handlers ...Handler) {
p.middleware = append(p.middleware, handlers...)
}
// UseFunc registers a HandlerFunc middleware
func (p *GardenParty) UseFunc(handlersFn ...HandlerFunc) {
p.Use(ConvertToHandlers(handlersFn)...)
}
// StaticHandlerFunc returns a HandlerFunc to serve static system directory
// Accepts 5 parameters
//
// first is the systemPath (string)
// Path to the root directory to serve files from.
//
// second is the stripSlashes (int) level
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
//
// third is the compress (bool)
// Transparently compresses responses if set to true.
//
// The server tries minimizing CPU usage by caching compressed files.
// It adds fasthttp.FSCompressedFileSuffix suffix to the original file name and
// tries saving the resulting compressed file under the new file name.
// So it is advisable to give the server write access to Root
// and to all inner folders in order to minimze CPU usage when serving
// compressed responses.
//
// fourth is the generateIndexPages (bool)
// Index pages for directories without files matching IndexNames
// are automatically generated if set.
//
// Directory index generation may be quite slow for directories
// with many files (more than 1K), so it is discouraged enabling
// index pages' generation for such directories.
//
// fifth is the indexNames ([]string)
// List of index file names to try opening during directory access.
//
// For example:
//
// * index.html
// * index.htm
// * my-super-index.xml
//
func (p *GardenParty) StaticHandlerFunc(systemPath string, stripSlashes int, compress bool, generateIndexPages bool, indexNames []string) HandlerFunc {
if indexNames == nil {
indexNames = []string{}
}
fs := &fasthttp.FS{
// Path to directory to serve.
Root: systemPath,
IndexNames: indexNames,
// Generate index pages if client requests directory contents.
GenerateIndexPages: generateIndexPages,
// Enable transparent compression to save network traffic.
Compress: compress,
CacheDuration: config.StaticCacheDuration,
CompressedFileSuffix: config.CompressedFileSuffix,
}
if stripSlashes > 0 {
fs.PathRewrite = fasthttp.NewPathSlashesStripper(stripSlashes)
}
// Create request handler for serving static files.
h := fs.NewRequestHandler()
return func(ctx *Context) {
h(ctx.RequestCtx)
errCode := ctx.RequestCtx.Response.StatusCode()
if errHandler := ctx.station.router.GetByCode(errCode); errHandler != nil {
ctx.RequestCtx.Response.ResetBody()
ctx.EmitError(errCode)
}
if ctx.pos < uint8(len(ctx.middleware))-1 {
ctx.Next() // for any case
}
}
}
// Static registers a route which serves a system directory
// this doesn't generates an index page which list all files
// no compression is used also, for these features look at StaticFS func
// accepts three parameters
// first parameter is the request url path (string)
// second parameter is the system directory (string)
// third parameter is the level (int) of stripSlashes
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
func (p *GardenParty) Static(relative string, systemPath string, stripSlashes int) {
if relative[len(relative)-1] != SlashByte { // if / then /*filepath, if /something then /something/*filepath
relative += "/"
}
h := p.StaticHandlerFunc(systemPath, stripSlashes, false, false, nil)
p.Get(relative+"*filepath", h)
p.Head(relative+"*filepath", h)
}
// StaticFS registers a route which serves a system directory
// this is the fastest method to serve static files
// generates an index page which list all files
// if you use this method it will generate compressed files also
// think this function as small fileserver with http
// accepts three parameters
// first parameter is the request url path (string)
// second parameter is the system directory (string)
// third parameter is the level (int) of stripSlashes
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
func (p *GardenParty) StaticFS(reqPath string, systemPath string, stripSlashes int) {
if reqPath[len(reqPath)-1] != SlashByte {
reqPath += "/"
}
h := p.StaticHandlerFunc(systemPath, stripSlashes, true, true, nil)
p.Get(reqPath+"*filepath", h)
p.Head(reqPath+"*filepath", h)
}
// StaticWeb same as Static but if index.html exists and request uri is '/' then display the index.html's contents
// accepts three parameters
// first parameter is the request url path (string)
// second parameter is the system directory (string)
// third parameter is the level (int) of stripSlashes
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
// * stripSlashes = 2, original path: "/foo/bar", result: ""
// * if you don't know what to put on stripSlashes just 1
func (p *GardenParty) StaticWeb(reqPath string, systemPath string, stripSlashes int) {
if reqPath[len(reqPath)-1] != SlashByte { // if / then /*filepath, if /something then /something/*filepath
reqPath += "/"
}
hasIndex := utils.Exists(systemPath + utils.PathSeparator + "index.html")
serveHandler := p.StaticHandlerFunc(systemPath, stripSlashes, false, !hasIndex, nil) // if not index.html exists then generate index.html which shows the list of files
indexHandler := func(ctx *Context) {
if len(ctx.Param("filepath")) < 2 && hasIndex {
ctx.Request.SetRequestURI("index.html")
}
ctx.Next()
}
p.Get(reqPath+"*filepath", indexHandler, serveHandler)
p.Head(reqPath+"*filepath", indexHandler, serveHandler)
}
// StaticServe serves a directory as web resource
// it's the simpliest form of the Static* functions
// Almost same usage as StaticWeb
// accepts only one required parameter which is the systemPath ( the same path will be used to register the GET&HEAD routes)
// if second parameter is empty, otherwise the requestPath is the second parameter
// it uses gzip compression (compression on each request, no file cache)
func (p *GardenParty) StaticServe(systemPath string, requestPath ...string) {
var reqPath string
if len(reqPath) > 0 {
reqPath = requestPath[0]
}
reqPath = strings.Replace(systemPath, utils.PathSeparator, Slash, -1) // replaces any \ to /
reqPath = strings.Replace(reqPath, "//", Slash, -1) // for any case, replaces // to /
reqPath = strings.Replace(reqPath, ".", "", -1) // replace any dots (./mypath -> /mypath)
p.Get(reqPath+"/*file", func(ctx *Context) {
filepath := ctx.Param("file")
path := strings.Replace(filepath, "/", utils.PathSeparator, -1)
path = absPath(systemPath, path)
if !utils.DirectoryExists(path) {
ctx.NotFound()
return
}
ctx.ServeFile(path, true)
})
}
/* here in order to the subdomains be able to change favicon also */
// Favicon serves static favicon
// accepts 2 parameters, second is optionally
// favPath (string), declare the system directory path of the __.ico
// requestPath (string), it's the route's path, by default this is the "/favicon.ico" because some browsers tries to get this by default first,
// you can declare your own path if you have more than one favicon (desktop, mobile and so on)
//
// this func will add a route for you which will static serve the /yuorpath/yourfile.ico to the /yourfile.ico (nothing special that you can't handle by yourself)
// Note that you have to call it on every favicon you have to serve automatically (dekstop, mobile and so on)
//
// returns an error if something goes bad
func (p *GardenParty) Favicon(favPath string, requestPath ...string) error {
f, err := os.Open(favPath)
if err != nil {
return ErrDirectoryFileNotFound.Format(favPath, err.Error())
}
defer f.Close()
fi, _ := f.Stat()
if fi.IsDir() { // if it's dir the try to get the favicon.ico
fav := path.Join(favPath, "favicon.ico")
f, err = os.Open(fav)
if err != nil {
//we try again with .png
return p.Favicon(path.Join(favPath, "favicon.png"))
}
favPath = fav
fi, _ = f.Stat()
}
modtime := fi.ModTime().UTC().Format(TimeFormat)
contentType := utils.TypeByExtension(favPath)
// copy the bytes here in order to cache and not read the ico on each request.
cacheFav := make([]byte, fi.Size())
if _, err = f.Read(cacheFav); err != nil {
return ErrDirectoryFileNotFound.Format(favPath, "Couldn't read the data bytes from ico: "+err.Error())
}
h := func(ctx *Context) {
if t, err := time.Parse(TimeFormat, ctx.RequestHeader(IfModifiedSince)); err == nil && fi.ModTime().Before(t.Add(config.StaticCacheDuration)) {
ctx.Response.Header.Del(ContentType)
ctx.Response.Header.Del(ContentLength)
ctx.SetStatusCode(StatusNotModified)
return
}
ctx.Response.Header.Set(ContentType, contentType)
ctx.Response.Header.Set(LastModified, modtime)
ctx.SetStatusCode(StatusOK)
ctx.Response.SetBody(cacheFav)
}
reqPath := "/favicon" + path.Ext(fi.Name()) //we could use the filename, but because standards is /favicon.ico/.png.
if len(requestPath) > 0 {
reqPath = requestPath[0]
}
p.Get(reqPath, h)
p.Head(reqPath, h)
return nil
}
// StaticContent serves bytes, memory cached, on the reqPath
func (p *GardenParty) StaticContent(reqPath string, contentType string, content []byte) {
modtime := time.Now()
modtimeStr := modtime.UTC().Format(TimeFormat)
h := func(ctx *Context) {
if t, err := time.Parse(TimeFormat, ctx.RequestHeader(IfModifiedSince)); err == nil && modtime.Before(t.Add(config.StaticCacheDuration)) {
ctx.Response.Header.Del(ContentType)
ctx.Response.Header.Del(ContentLength)
ctx.SetStatusCode(StatusNotModified)
return
}
ctx.Response.Header.Set(ContentType, contentType)
ctx.Response.Header.Set(LastModified, modtimeStr)
ctx.SetStatusCode(StatusOK)
ctx.Response.SetBody(content)
}
p.Get(reqPath, h)
p.Head(reqPath, h)
}
/* */
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
// Party can also be named as 'Join' or 'Node' or 'Group' , Party chosen because it has more fun
func (p *GardenParty) Party(path string, handlersFn ...HandlerFunc) IParty {
middleware := ConvertToHandlers(handlersFn)
if path[0] != SlashByte && strings.Contains(path, ".") {
//it's domain so no handlers share (even the global ) or path, nothing.
} else {
// set path to parent+child
path = absPath(p.relativePath, path)
// append the parent's +child's handlers
middleware = JoinMiddleware(p.middleware, middleware)
}
return &GardenParty{relativePath: path, station: p.station, middleware: middleware}
}
func absPath(rootPath string, relativePath string) (absPath string) {
if relativePath == "" {
absPath = rootPath
} else {
absPath = path.Join(rootPath, relativePath)
}
return
}
// fixPath fix the double slashes, (because of root,I just do that before the .Handle no need for anything else special)
func fixPath(str string) string {
strafter := strings.Replace(str, "//", Slash, -1)
if strafter[0] == SlashByte && strings.Count(strafter, ".") >= 2 {
//it's domain, remove the first slash
strafter = strafter[1:]
}
return strafter
}

394
plugin.go Normal file
View File

@ -0,0 +1,394 @@
package iris
import (
"fmt"
"github.com/kataras/iris/utils"
)
type (
// IPlugin just an empty base for plugins
// A Plugin can be added with: .Add(PreHandleFunc(func(IRoute))) and so on... or
// .Add(myPlugin{}) which myPlugin is a struct with any of the methods below or
// .PreHandle(PreHandleFunc), .PostHandle(func(IRoute)) and so on...
IPlugin interface {
}
// IPluginGetName implements the GetName() string method
IPluginGetName interface {
// GetName has to returns the name of the plugin, a name is unique
// name has to be not dependent from other methods of the plugin,
// because it is being called even before the Activate
GetName() string
}
// IPluginGetDescription implements the GetDescription() string method
IPluginGetDescription interface {
// GetDescription has to returns the description of what the plugins is used for
GetDescription() string
}
// IPluginActivate implements the Activate(IPluginContainer) error method
IPluginActivate interface {
// Activate called BEFORE the plugin being added to the plugins list,
// if Activate returns none nil error then the plugin is not being added to the list
// it is being called only one time
//
// PluginContainer parameter used to add other plugins if that's necessary by the plugin
Activate(IPluginContainer) error
}
// IPluginPreHandle implements the PreHandle(IRoute) method
IPluginPreHandle interface {
// PreHandle it's being called every time BEFORE a Route is registed to the Router
//
// parameter is the Route
PreHandle(IRoute)
}
PreHandleFunc func(IRoute)
// IPluginPostHandle implements the PostHandle(IRoute) method
IPluginPostHandle interface {
// PostHandle it's being called every time AFTER a Route successfully registed to the Router
//
// parameter is the Route
PostHandle(IRoute)
}
PostHandleFunc func(IRoute)
// IPluginPreListen implements the PreListen(*Iris) method
IPluginPreListen interface {
// PreListen it's being called only one time, BEFORE the Server is started (if .Listen called)
// is used to do work at the time all other things are ready to go
// parameter is the station
PreListen(*Iris)
}
PreListenFunc func(*Iris)
// IPluginPostListen implements the PostListen(*Iris) method
IPluginPostListen interface {
// PostListen it's being called only one time, AFTER the Server is started (if .Listen called)
// parameter is the station
PostListen(*Iris)
}
PostListenFunc func(*Iris)
// IPluginPreClose implements the PreClose(*Iris) method
IPluginPreClose interface {
// PreClose it's being called only one time, BEFORE the Iris .Close method
// any plugin cleanup/clear memory happens here
//
// The plugin is deactivated after this state
PreClose(*Iris)
}
PreCloseFunc func(*Iris)
// IPluginPreDownload It's for the future, not being used, I need to create
// and return an ActivatedPlugin type which will have it's methods, and pass it on .Activate
// but now we return the whole pluginContainer, which I can't determinate which plugin tries to
// download something, so we will leave it here for the future.
IPluginPreDownload interface {
// PreDownload it's being called every time a plugin tries to download something
//
// first parameter is the plugin
// second parameter is the download url
// must return a boolean, if false then the plugin is not permmited to download this file
PreDownload(plugin IPlugin, downloadURL string) // bool
}
PreDownloadFunc func(IPlugin, string)
// IPluginContainer is the interface which the PluginContainer should implements
IPluginContainer interface {
Add(plugin IPlugin) error
Remove(pluginName string) error
GetName(plugin IPlugin) string
GetDescription(plugin IPlugin) string
GetByName(pluginName string) IPlugin
Printf(format string, a ...interface{})
DoPreHandle(route IRoute)
DoPostHandle(route IRoute)
DoPreListen(station *Iris)
DoPostListen(station *Iris)
DoPreClose(station *Iris)
DoPreDownload(pluginTryToDownload IPlugin, downloadURL string)
GetAll() []IPlugin
// GetDownloader is the only one module that is used and fire listeners at the same time in this file
GetDownloader() IDownloadManager
}
// IDownloadManager is the interface which the DownloadManager should implements
IDownloadManager interface {
DirectoryExists(dir string) bool
DownloadZip(zipURL string, targetDir string) (string, error)
Unzip(archive string, target string) (string, error)
Remove(filePath string) error
// 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)
}
// DownloadManager is just a struch which exports the util's downloadZip, directoryExists, unzip methods, used by the plugins via the PluginContainer
DownloadManager struct {
}
)
// convert the functions to IPlugin
func (fn PreHandleFunc) PreHandle(route IRoute) {
fn(route)
}
func (fn PostHandleFunc) PostHandle(route IRoute) {
fn(route)
}
func (fn PreListenFunc) PreListen(station *Iris) {
fn(station)
}
func (fn PostListenFunc) PostListen(station *Iris) {
fn(station)
}
func (fn PreCloseFunc) PreClose(station *Iris) {
fn(station)
}
func (fn PreDownloadFunc) PreDownload(pl IPlugin, downloadURL string) {
fn(pl, downloadURL)
}
//
var _ IDownloadManager = &DownloadManager{}
var _ IPluginContainer = &PluginContainer{}
// DirectoryExists returns true if a given local directory exists
func (d *DownloadManager) DirectoryExists(dir string) bool {
return utils.DirectoryExists(dir)
}
// DownloadZip downlodas a zip to the given local path location
func (d *DownloadManager) DownloadZip(zipURL string, targetDir string) (string, error) {
return utils.DownloadZip(zipURL, targetDir)
}
// Unzip unzips a zip to the given local path location
func (d *DownloadManager) Unzip(archive string, target string) (string, error) {
return utils.Unzip(archive, target)
}
// Remove deletes/removes/rm a file
func (d *DownloadManager) Remove(filePath string) error {
return utils.RemoveFile(filePath)
}
// Install is just the flow of the: DownloadZip->Unzip->Remove the zip
func (d *DownloadManager) Install(remoteFileZip string, targetDirectory string) (string, error) {
return utils.Install(remoteFileZip, targetDirectory)
}
// PluginContainer is the base container of all Iris, registed plugins
type PluginContainer struct {
activatedPlugins []IPlugin
downloader *DownloadManager
}
// Add activates the plugins and if succeed then adds it to the activated plugins list
func (p *PluginContainer) Add(plugin IPlugin) error {
if p.activatedPlugins == nil {
p.activatedPlugins = make([]IPlugin, 0)
}
// Check if it's a plugin first, has Activate GetName
// Check if the plugin already exists
pName := p.GetName(plugin)
if pName != "" && p.GetByName(pName) != nil {
return ErrPluginAlreadyExists.Format(pName, p.GetDescription(plugin))
}
// Activate the plugin, if no error then add it to the plugins
if pluginObj, ok := plugin.(IPluginActivate); ok {
err := pluginObj.Activate(p)
if err != nil {
return ErrPluginActivate.Format(pName, err.Error())
}
}
// 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.Return()
}
if pluginName == "" {
//return error: cannot delete an unamed plugin
return ErrPluginRemoveEmptyName.Return()
}
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.Return()
}
p.activatedPlugins = append(p.activatedPlugins[:indexToRemove], p.activatedPlugins[indexToRemove+1:]...)
return nil
}
// GetName returns the name of a plugin, if no GetName() implemented it returns an empty string ""
func (p *PluginContainer) GetName(plugin IPlugin) string {
if pluginObj, ok := plugin.(IPluginGetName); ok {
return pluginObj.GetName()
}
return ""
}
// GetDescription returns the name of a plugin, if no GetDescription() implemented it returns an empty string ""
func (p *PluginContainer) GetDescription(plugin IPlugin) string {
if pluginObj, ok := plugin.(IPluginGetDescription); ok {
return pluginObj.GetDescription()
}
return ""
}
// GetByName returns a plugin instance by it's name
func (p *PluginContainer) GetByName(pluginName string) IPlugin {
if p.activatedPlugins == nil {
return nil
}
for i := range p.activatedPlugins {
if pluginObj, ok := p.activatedPlugins[i].(IPluginGetName); ok {
if pluginObj.GetName() == pluginName {
return pluginObj
}
}
}
return nil
}
// GetAll returns all activated plugins
func (p *PluginContainer) GetAll() []IPlugin {
return p.activatedPlugins
}
// GetDownloader returns the download manager
func (p *PluginContainer) GetDownloader() IDownloadManager {
// create it if and only if it used somewhere
if p.downloader == nil {
p.downloader = &DownloadManager{}
}
return p.downloader
}
// Printf sends plain text to any registed logger (future), some plugins maybe want use this method
// maybe at the future I change it, instead of sync even-driven to async channels...
func (p *PluginContainer) Printf(format string, a ...interface{}) {
fmt.Printf(format, a...) //for now just this.
}
// PreHandle adds a PreHandle plugin-function to the plugin flow container
func (p *PluginContainer) PreHandle(fn PreHandleFunc) {
p.Add(fn)
}
// DoPreHandle raise all plugins which has the PreHandle method
func (p *PluginContainer) DoPreHandle(route IRoute) {
for i := range p.activatedPlugins {
// check if this method exists on our plugin obj, these are optionaly and call it
if pluginObj, ok := p.activatedPlugins[i].(IPluginPreHandle); ok {
pluginObj.PreHandle(route)
}
}
}
// PostHandle adds a PostHandle plugin-function to the plugin flow container
func (p *PluginContainer) PostHandle(fn PostHandleFunc) {
p.Add(fn)
}
// DoPostHandle raise all plugins which has the DoPostHandle method
func (p *PluginContainer) DoPostHandle(route IRoute) {
for i := range p.activatedPlugins {
// check if this method exists on our plugin obj, these are optionaly and call it
if pluginObj, ok := p.activatedPlugins[i].(IPluginPostHandle); ok {
pluginObj.PostHandle(route)
}
}
}
// PreListen adds a PreListen plugin-function to the plugin flow container
func (p *PluginContainer) PreListen(fn PreListenFunc) {
p.Add(fn)
}
// DoPreListen raise all plugins which has the DoPreListen method
func (p *PluginContainer) DoPreListen(station *Iris) {
for i := range p.activatedPlugins {
// check if this method exists on our plugin obj, these are optionaly and call it
if pluginObj, ok := p.activatedPlugins[i].(IPluginPreListen); ok {
pluginObj.PreListen(station)
}
}
}
// 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 *Iris) {
for i := range p.activatedPlugins {
// check if this method exists on our plugin obj, these are optionaly and call it
if pluginObj, ok := p.activatedPlugins[i].(IPluginPostListen); ok {
pluginObj.PostListen(station)
}
}
}
// 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 *Iris) {
for i := range p.activatedPlugins {
// check if this method exists on our plugin obj, these are optionaly and call it
if pluginObj, ok := p.activatedPlugins[i].(IPluginPreClose); ok {
pluginObj.PreClose(station)
}
}
}
// 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 IPlugin, downloadURL string) {
for i := range p.activatedPlugins {
// check if this method exists on our plugin obj, these are optionaly and call it
if pluginObj, ok := p.activatedPlugins[i].(IPluginPreDownload); ok {
pluginObj.PreDownload(pluginTryToDownload, downloadURL)
}
}
}

45
plugin/editor/README.md Normal file
View File

@ -0,0 +1,45 @@
## Package information
Editor Plugin is just a bridge between Iris and [alm-tools](http://alm.tools).
[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.
This plugin starts it's own server, if Iris server is using TLS then the editor will use the same key and cert.
## How to use
```go
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/plugin/editor"
)
func main(){
e := editor.New("username","password").Port(4444).Dir("/path/to/the/client/side/directory")
iris.Plugins().Add(e)
iris.Get("/", func (ctx *iris.Context){})
iris.Listen(":8080")
}
```
> Note for username, password: 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. [Read more.](https://www.httpwatch.com/httpgallery/authentication/)
> The editor can't work if the directory doesn't contains a [tsconfig.json](http://www.typescriptlang.org/docs/handbook/tsconfig.json.html).
> If you are using the [typescript plugin](https://github.com/kataras/iris/tree/development/plugin/typescript) you don't have to call the .Dir(...)

157
plugin/editor/editor.go Normal file
View File

@ -0,0 +1,157 @@
package editor
/* Notes for Auth
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 (
"os"
"strconv"
"strings"
"github.com/kataras/iris"
"github.com/kataras/iris/config"
"github.com/kataras/iris/logger"
"github.com/kataras/iris/npm"
"github.com/kataras/iris/utils"
)
const (
// Name the name of the Plugin, which is "EditorPlugin"
Name = "EditorPlugin"
)
type (
// Plugin is an Editor Plugin the struct which implements the iris.IPlugin
// 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
Plugin struct {
config *config.Editor
logger *logger.Logger
enabled bool // default true
keyfile string
certfile string
// after alm started
process *os.Process
}
)
// New creates and returns an Editor Plugin instance
func New(cfg ...config.Editor) *Plugin {
c := config.DefaultEditor().Merge(cfg)
e := &Plugin{enabled: true, config: &c}
return e
}
// User set a user, accepts two parameters: username (string), string (string)
func (e *Plugin) User(username string, password string) *Plugin {
e.config.Username = username
e.config.Password = password
return e
}
// Dir sets the directory which the client side source code alive
func (e *Plugin) Dir(workingDir string) *Plugin {
e.config.WorkingDir = workingDir
return e
}
// Port sets the port (int) for the editor plugin's standalone server
func (e *Plugin) Port(port int) *Plugin {
e.config.Port = port
return e
}
//
// SetEnable if true enables the editor plugin, otherwise disables it
func (e *Plugin) SetEnable(enable bool) {
e.enabled = enable
}
// GetName returns the name of the Plugin
func (e *Plugin) GetName() string {
return Name
}
// GetDescription EditorPlugin is a bridge between Iris and the alm-tools, the browser-based IDE for client-side sources.
func (e *Plugin) GetDescription() string {
return Name + " is a bridge between Iris and the alm-tools, the browser-based IDE for client-side sources. \n"
}
// PreListen runs before the server's listens, saves the keyfile,certfile and the host from the Iris station to listen for
func (e *Plugin) PreListen(s *iris.Iris) {
e.logger = s.Logger()
e.keyfile = s.Server().Config.KeyFile
e.certfile = s.Server().Config.CertFile
if e.config.Host == "" {
h := s.Server().Config.ListeningAddr
if idx := strings.Index(h, ":"); idx >= 0 {
h = h[0:idx]
}
if h == "" {
h = "127.0.0.1"
}
e.config.Host = h
}
e.start()
}
// PreClose kills the editor's server when Iris is closed
func (e *Plugin) PreClose(s *iris.Iris) {
if e.process != nil {
err := e.process.Kill()
if err != nil {
e.logger.Printf("\nError while trying to terminate the (Editor)Plugin, please kill this process by yourself, process id: %d", e.process.Pid)
}
}
}
// start starts the job
func (e *Plugin) start() {
if e.config.Username == "" || e.config.Password == "" {
e.logger.Println("Error before running alm-tools. You have to set username & password for security reasons, otherwise this plugin won't run.")
return
}
if !npm.Exists("alm/bin/alm") {
e.logger.Println("Installing alm-tools, please wait...")
res := npm.Install("alm")
if res.Error != nil {
e.logger.Print(res.Error.Error())
return
}
e.logger.Print(res.Message)
}
cmd := utils.CommandBuilder("node", npm.Abs("alm/src/server.js"))
cmd.AppendArguments("-a", e.config.Username+":"+e.config.Password, "-h", e.config.Host, "-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.keyfile != "" && e.certfile != "" {
cmd.AppendArguments("--httpskey", e.keyfile, "--httpscert", e.certfile)
}
//For debug only:
//cmd.Stdout = os.Stdout
//cmd.Stderr = os.Stderr
//os.Stdin = os.Stdin
err := cmd.Start()
if err != nil {
e.logger.Println("Error while running alm-tools. Trace: " + err.Error())
return
}
//we lose the internal error handling but ok...
e.logger.Printf("Editor is running at %s:%d | %s", e.config.Host, e.config.Port, e.config.WorkingDir)
}

View File

@ -0,0 +1,48 @@
## Iris Control
### THIS IS NOT READY YET
This plugin will give you remotely ( and local ) access to your iris server's information via a web interface
### Assets
No assets here because this is go -getable folder I don't want to messup with the folder size, in order to solve this
I created a downloader manager inside this package which downloads the first time the assets and unzip them to the kataras/iris/plugin/iris-control/iris-control-assets/ .
The assets files are inside [this repository](https://github.com/iris-contrib/iris-control-assets)
## How to use
```go
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/plugin/iriscontrol"
"fmt"
)
func main() {
iris.Plugins().Add(iriscontrol.Web(9090, map[string]string{
"irisusername1": "irispassword1",
"irisusername2": "irispassowrd2",
}))
iris.Get("/", func(ctx *iris.Context) {
})
iris.Post("/something", func(ctx *iris.Context) {
})
fmt.Printf("Iris is listening on :%d", 8080)
iris.Listen(":8080")
}
```

View File

@ -0,0 +1,105 @@
package iriscontrol
import (
"os"
"strconv"
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/plugin/routesinfo"
)
var pathSeperator = string(os.PathSeparator)
var pluginPath = os.Getenv("GOPATH") + pathSeperator + "src" + pathSeperator + "github.com" + pathSeperator + "kataras" + pathSeperator + "iris" + pathSeperator + "plugin" + pathSeperator + "iriscontrol" + pathSeperator
var assetsURL = "https://github.com/iris-contrib/iris-control-assets/archive/master.zip"
var assetsFolderName = "iris-control-assets-master"
var installationPath = pluginPath + assetsFolderName + pathSeperator
// for the plugin server
func (i *irisControlPlugin) startControlPanel() {
// install the assets first
if err := i.installAssets(); err != nil {
i.pluginContainer.Printf("[%s] %s Error %s: Couldn't install the assets from the internet,\n make sure you are connecting to the internet the first time running the iris-control plugin", time.Now().UTC().String(), Name, err.Error())
i.Destroy()
return
}
i.server = iris.New()
i.server.Config().Render.Template.Directory = installationPath + "templates"
//i.server.SetRenderConfig(i.server.Config.Render)
i.setPluginsInfo()
i.setPanelRoutes()
go i.server.Listen(strconv.Itoa(i.options.Port))
i.pluginContainer.Printf("[%s] %s is running at port %d with %d authenticated users", time.Now().UTC().String(), Name, i.options.Port, len(i.auth.authenticatedUsers))
}
// DashboardPage is the main data struct for the index
// contains a boolean if server is running, the routes and the plugins
type DashboardPage struct {
ServerIsRunning bool
Routes []routesinfo.RouteInfo
Plugins []PluginInfo
}
func (i *irisControlPlugin) setPluginsInfo() {
plugins := i.pluginContainer.GetAll()
i.plugins = make([]PluginInfo, 0, len(plugins))
for _, plugin := range plugins {
i.plugins = append(i.plugins, PluginInfo{Name: i.pluginContainer.GetName(plugin), Description: i.pluginContainer.GetDescription(plugin)})
}
}
// installAssets checks if must install ,if yes download the zip and unzip it, returns error.
func (i *irisControlPlugin) installAssets() (err error) {
//we know already what is the zip folder inside it, so we can check if it's exists, if yes then don't install it again.
if i.pluginContainer.GetDownloader().DirectoryExists(installationPath) {
return
}
//set the installationPath ,although we know it but do it here too
installationPath, err = i.pluginContainer.GetDownloader().Install(assetsURL, pluginPath)
return err
}
func (i *irisControlPlugin) setPanelRoutes() {
i.server.Static("/public", installationPath+"static", 1)
i.server.Get("/login", func(ctx *iris.Context) {
ctx.Render("login", nil)
})
i.server.Post("/login", func(ctx *iris.Context) {
i.auth.login(ctx)
})
i.server.Use(i.auth)
i.server.Get("/", func(ctx *iris.Context) {
ctx.Render("index", DashboardPage{ServerIsRunning: i.station.Server().IsListening(), Routes: i.routes.All(), Plugins: i.plugins})
})
i.server.Post("/logout", func(ctx *iris.Context) {
i.auth.logout(ctx)
})
//the controls
i.server.Post("/start_server", func(ctx *iris.Context) {
//println("server start")
old := i.stationServer
if !old.IsSecure() {
i.station.Listen(old.Config.ListeningAddr)
//yes but here it does re- post listen to this plugin so ...
} else {
i.station.ListenTLS(old.Config.ListeningAddr, old.Config.CertFile, old.Config.KeyFile)
}
})
i.server.Post("/stop_server", func(ctx *iris.Context) {
//println("server stop")
i.station.Close()
})
}

View File

@ -0,0 +1,11 @@
package iriscontrol
// NOT READY YET
// PluginInfo holds the Name and the description of the registed plugins
type PluginInfo struct {
Name string
Description string
}
//func getPluginlist...

View File

@ -0,0 +1,112 @@
package iriscontrol
import (
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/config"
"github.com/kataras/iris/plugin/routesinfo"
"github.com/kataras/iris/server"
)
// Name the name(string) of this plugin which is Iris Control
const Name = "Iris Control"
type irisControlPlugin struct {
options config.IrisControl
// the pluginContainer is the container which keeps this plugin from the main user's iris instance
pluginContainer iris.IPluginContainer
// the station object of the main user's iris instance
station *iris.Iris
//a copy of the server which the main user's iris is listening for
stationServer *server.Server
// the server is this plugin's server object, it is managed by this plugin only
server *iris.Iris
//
//infos
routes *routesinfo.Plugin
plugins []PluginInfo
//
auth *userAuth
}
// New returns the plugin which is ready-to-use inside iris.Plugin method
// receives config.IrisControl
func New(cfg ...config.IrisControl) iris.IPlugin {
c := config.DefaultIrisControl()
if len(cfg) > 0 {
c = cfg[0]
}
auth := newUserAuth(c.Users)
if auth == nil {
panic(Name + " Error: you should pass authenticated users map to the options, refer to the docs!")
}
return &irisControlPlugin{options: c, auth: auth, routes: routesinfo.RoutesInfo()}
}
// Web set the options for the plugin and return the plugin which is ready-to-use inside iris.Plugin method
// first parameter is port
// second parameter is map of users (username:password)
func Web(port int, users map[string]string) iris.IPlugin {
return New(config.IrisControl{port, users})
}
// implement the base IPlugin
func (i *irisControlPlugin) Activate(container iris.IPluginContainer) error {
i.pluginContainer = container
container.Add(i.routes) // add the routesinfo plugin to the main server
return nil
}
func (i irisControlPlugin) GetName() string {
return Name
}
func (i irisControlPlugin) GetDescription() string {
return Name + " is just a web interface which gives you control of your Iris.\n"
}
//
// implement the rest of the plugin
// PostHandle
func (i *irisControlPlugin) PostHandle(route iris.IRoute) {
}
// PostListen sets the station object after the main server starts
// starts the actual work of the plugin
func (i *irisControlPlugin) PostListen(s *iris.Iris) {
//if the first time, because other times start/stop of the server so listen and no listen will be only from the control panel
if i.station == nil {
i.station = s
i.stationServer = i.station.Server()
i.startControlPanel()
}
}
func (i *irisControlPlugin) PreClose(s *iris.Iris) {
// Do nothing. This is a wrapper of the main server if we destroy when users stop the main server then we cannot continue the control panel i.Destroy()
}
//
// Destroy removes entirely the plugin, the options and all of these properties, you cannot re-use this plugin after this method.
func (i *irisControlPlugin) Destroy() {
i.pluginContainer.Remove(Name)
i.options = config.IrisControl{}
i.routes = nil
i.station = nil
i.server.Close()
i.pluginContainer = nil
i.auth.Destroy()
i.auth = nil
i.pluginContainer.Printf("[%s] %s is turned off", time.Now().UTC().String(), Name)
}

View File

@ -0,0 +1,20 @@
package iriscontrol
// for the main server
func (i *irisControlPlugin) StartServer() {
if i.station.Server().IsListening() == false {
if i.station.Server().IsSecure() {
//listen with ListenTLS
i.station.ListenTLS(i.station.Server().Config.ListeningAddr, i.station.Server().Config.CertFile, i.station.Server().Config.KeyFile)
} else {
//listen normal
i.station.Listen(i.station.Server().Config.ListeningAddr)
}
}
}
func (i *irisControlPlugin) StopServer() {
if i.station.Server().IsListening() {
i.station.Close()
}
}

View File

@ -0,0 +1,97 @@
package iriscontrol
import (
"strings"
"github.com/kataras/iris"
"github.com/kataras/iris/sessions"
// _ empty because it auto-registers
_ "github.com/kataras/iris/sessions/providers/memory"
)
var panelSessions *sessions.Manager
func init() {
//using the default
panelSessions = sessions.New()
}
type user struct {
username string
password string
}
type userAuth struct {
authenticatedUsers []user
}
// newUserAuth returns a new userAuth object, parameter is the authenticated users as map
func newUserAuth(usersMap map[string]string) *userAuth {
if usersMap != nil {
obj := &userAuth{make([]user, 0)}
for key, val := range usersMap {
obj.authenticatedUsers = append(obj.authenticatedUsers, user{key, val})
}
return obj
}
return nil
}
func (u *userAuth) login(ctx *iris.Context) {
session := panelSessions.Start(ctx)
username := ctx.PostFormValue("username")
password := ctx.PostFormValue("password")
for _, authenticatedUser := range u.authenticatedUsers {
if authenticatedUser.username == username && authenticatedUser.password == password {
session.Set("username", username)
session.Set("password", password)
ctx.Write("success")
return
}
}
ctx.Write("fail")
}
func (u *userAuth) logout(ctx *iris.Context) {
session := panelSessions.Start(ctx)
session.Set("user", nil)
ctx.Redirect("/login")
}
// check if session stored, then check if this user is the correct, each time, then continue, else not
func (u *userAuth) Serve(ctx *iris.Context) {
if ctx.PathString() == "/login" || strings.HasPrefix(ctx.PathString(), "/public") {
ctx.Next()
return
}
session := panelSessions.Start(ctx)
if sessionVal := session.Get("username"); sessionVal != nil {
username := sessionVal.(string)
password := session.GetString("password")
if username != "" && password != "" {
for _, authenticatedUser := range u.authenticatedUsers {
if authenticatedUser.username == username && authenticatedUser.password == password {
ctx.Next()
return
}
}
}
}
//if not logged in the redirect to the /login
ctx.Redirect("/login")
}
// Destroy this is called on PreClose by the iriscontrol.go
func (u *userAuth) Destroy() {
}

View File

@ -0,0 +1,61 @@
## RoutesInfo plugin
This plugin collects & stores all registered routes and gives information about them.
#### The RouteInfo
```go
type RouteInfo struct {
Method string
Domain string
Path string
RegistedAt time.Time
}
```
## How to use
```go
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/plugin/routesinfo"
)
func main() {
info := routesinfo.New()
iris.Plugins().Add(info)
iris.Get("/yourpath", func(c *iris.Context) {
c.Write("yourpath")
})
iris.Post("/otherpostpath", func(c *iris.Context) {
c.Write("other post path")
})
all := info.All()
// allget := info.ByMethod("GET") -> slice
// alllocalhost := info.ByDomain("localhost") -> slice
// bypath:= info.ByPath("/yourpath") -> slice
// bydomainandmethod:= info.ByDomainAndMethod("localhost","GET") -> slice
// bymethodandpath:= info.ByMethodAndPath("GET","/yourpath") -> single (it could be slice for all domains too but it's not)
println("The first registed route was: ", all[0].Path, "registed at: ", all[0].RegistedAt.String())
println("All routes info:")
for i:= range all {
println(all[i].String())
//outputs->
// Domain: localhost Method: GET Path: /yourpath RegistedAt: 2016/03/27 15:27:05:029 ...
// Domain: localhost Method: POST Path: /otherpostpath RegistedAt: 2016/03/27 15:27:05:030 ...
}
iris.Listen(":8080")
}
```

View File

@ -0,0 +1,151 @@
package routesinfo
import (
"fmt"
"strings"
"time"
"github.com/kataras/iris"
)
//Name the name of the plugin, is "RoutesInfo"
const Name = "RoutesInfo"
// RouteInfo holds the method, domain, path and registered time of a route
type RouteInfo struct {
Method string
Domain string
Path string
RegistedAt time.Time
}
// String returns the string presentation of the Route(Info)
func (ri RouteInfo) String() string {
if ri.Domain == "" {
ri.Domain = "localhost" // only for printing, this doesn't save it, no pointer.
}
return fmt.Sprintf("Domain: %s Method: %s Path: %s RegistedAt: %s", ri.Domain, ri.Method, ri.Path, ri.RegistedAt.String())
}
// Plugin the routes info plugin, holds the routes as RouteInfo objects
type Plugin struct {
routes []RouteInfo
}
// implement the base IPlugin
// GetName ...
func (r Plugin) GetName() string {
return Name
}
// GetDescription RoutesInfo gives information about the registed routes
func (r Plugin) GetDescription() string {
return Name + " gives information about the registed routes.\n"
}
//
// implement the rest of the plugin
// PostHandle collect the registed routes information
func (r *Plugin) PostHandle(route iris.IRoute) {
if r.routes == nil {
r.routes = make([]RouteInfo, 0)
}
r.routes = append(r.routes, RouteInfo{route.GetMethod(), route.GetDomain(), route.GetPath(), time.Now()})
}
// All returns all routeinfos
// returns a slice
func (r Plugin) All() []RouteInfo {
return r.routes
}
// ByDomain returns all routeinfos which registed to a specific domain
// returns a slice, if nothing founds this slice has 0 len&cap
func (r Plugin) ByDomain(domain string) []RouteInfo {
var routesByDomain []RouteInfo
rlen := len(r.routes)
if domain == "localhost" || domain == "127.0.0.1" || domain == ":" {
domain = ""
}
for i := 0; i < rlen; i++ {
if r.routes[i].Domain == domain {
routesByDomain = append(routesByDomain, r.routes[i])
}
}
return routesByDomain
}
// ByMethod returns all routeinfos by a http method
// returns a slice, if nothing founds this slice has 0 len&cap
func (r Plugin) ByMethod(method string) []RouteInfo {
var routesByMethod []RouteInfo
rlen := len(r.routes)
method = strings.ToUpper(method)
for i := 0; i < rlen; i++ {
if r.routes[i].Method == method {
routesByMethod = append(routesByMethod, r.routes[i])
}
}
return routesByMethod
}
// ByPath returns all routeinfos by a path
// maybe one path is the same on GET and POST ( for example /login GET, /login POST)
// because of that it returns a slice and not only one RouteInfo
// returns a slice, if nothing founds this slice has 0 len&cap
func (r Plugin) ByPath(path string) []RouteInfo {
var routesByPath []RouteInfo
rlen := len(r.routes)
for i := 0; i < rlen; i++ {
if r.routes[i].Path == path {
routesByPath = append(routesByPath, r.routes[i])
}
}
return routesByPath
}
// ByDomainAndMethod returns all routeinfos registed to a specific domain and has specific http method
// returns a slice, if nothing founds this slice has 0 len&cap
func (r Plugin) ByDomainAndMethod(domain string, method string) []RouteInfo {
var routesByDomainAndMethod []RouteInfo
rlen := len(r.routes)
method = strings.ToUpper(method)
if domain == "localhost" || domain == "127.0.0.1" || domain == ":" {
domain = ""
}
for i := 0; i < rlen; i++ {
if r.routes[i].Method == method && r.routes[i].Domain == domain {
routesByDomainAndMethod = append(routesByDomainAndMethod, r.routes[i])
}
}
return routesByDomainAndMethod
}
// ByMethodAndPath returns a single *RouteInfo which has specific http method and path
// returns only the first match
// if nothing founds returns nil
func (r Plugin) ByMethodAndPath(method string, path string) *RouteInfo {
rlen := len(r.routes)
for i := 0; i < rlen; i++ {
if r.routes[i].Method == method && r.routes[i].Path == path {
return &r.routes[i]
}
}
return nil
}
//
// RoutesInfo returns the Plugin, same as New()
func RoutesInfo() *Plugin {
return &Plugin{}
}
// New returns the Plugin, same as RoutesInfo()
func New() *Plugin {
return &Plugin{}
}

View File

@ -0,0 +1,83 @@
## 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/'
## 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/kataras/iris/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.
*/
ts := typescript.Options {
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:
ts = typescript.DefaultOptions()
//
iris.Plugins().Add(typescript.New(ts)) //or with the default options just: typescript.New()
iris.Get("/", func (ctx *iris.Context){})
iris.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.Options {
//...
Editor: typescript.Editor("username","passowrd")
//...
}
```
> [Read more](https://github.com/kataras/iris/tree/development/plugin/editor) for Editor

View File

@ -0,0 +1,102 @@
package typescript
import (
"encoding/json"
"io/ioutil"
"reflect"
)
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"`
}
)
// 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
func (tsconfig *Tsconfig) CompilerArgs() []string {
val := reflect.ValueOf(tsconfig).Elem().FieldByName("CompilerOptions")
compilerOpts := make([]string, val.NumField())
for i := 0; i < val.NumField(); i++ {
typeField := val.Type().Field(i)
compilerOpts[i] = "--" + typeField.Tag.Get("json")
}
return compilerOpts
}
// FromFile reads a file & returns the Tsconfig by its contents
func FromFile(tsConfigAbsPath string) *Tsconfig {
file, err := ioutil.ReadFile(tsConfigAbsPath)
if err != nil {
panic("[IRIS TypescriptPlugin.FromFile]" + err.Error())
}
config := &Tsconfig{}
json.Unmarshal(file, config)
return config
}
// 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: "es5",
NoImplicitAny: false,
SourceMap: false,
},
Exclude: []string{"node_modules"},
}
}

View File

@ -0,0 +1,300 @@
package typescript
import (
"errors"
"os"
"path/filepath"
"strings"
"github.com/kataras/iris"
"github.com/kataras/iris/config"
"github.com/kataras/iris/logger"
"github.com/kataras/iris/npm"
"github.com/kataras/iris/plugin/editor"
"github.com/kataras/iris/utils"
)
/* Notes
The editor is working when the typescript plugin finds a typescript project (tsconfig.json),
also working only if one typescript project found (normaly is one for client-side).
*/
// Name the name of the plugin, is "TypescriptPlugin"
const Name = "TypescriptPlugin"
var nodeModules = utils.PathSeparator + "node_modules" + utils.PathSeparator
type (
// Options the struct which holds the TypescriptPlugin options
// 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
Options struct {
Bin string
Dir string
Ignore string
Tsconfig *Tsconfig
Editor *editor.Plugin // the editor is just a plugin also
}
// Plugin the struct of the Typescript Plugin, holds all necessary fields & methods
Plugin struct {
options Options
// taken from Activate
pluginContainer iris.IPluginContainer
// taken at the PreListen
logger *logger.Logger
}
)
// Editor is just a shortcut for github.com/kataras/iris/plugin/editor.New()
// returns a new (Editor)Plugin, it's exists here because the typescript plugin has direct interest with the EditorPlugin
func Editor(username, password string) *editor.Plugin {
editorCfg := config.DefaultEditor()
editorCfg.Username = username
editorCfg.Password = password
return editor.New(editorCfg)
}
// DefaultOptions returns the default Options of the Plugin
func DefaultOptions() Options {
root, err := os.Getwd()
if err != nil {
panic("Typescript Plugin: Cannot get the Current Working Directory !!! [os.getwd()]")
}
opt := Options{Dir: root + utils.PathSeparator, Ignore: nodeModules, Tsconfig: DefaultTsconfig()}
opt.Bin = npm.Abs("typescript/lib/tsc.js")
return opt
}
// Plugin
// New creates & returns a new instnace typescript plugin
func New(_opt ...Options) *Plugin {
var options = DefaultOptions()
if _opt != nil && len(_opt) > 0 { //not nil always but I like this way :)
opt := _opt[0]
if opt.Bin != "" {
options.Bin = opt.Bin
}
if opt.Dir != "" {
options.Dir = opt.Dir
}
if !strings.Contains(opt.Ignore, nodeModules) {
opt.Ignore += "," + nodeModules
}
if opt.Tsconfig != nil {
options.Tsconfig = opt.Tsconfig
}
options.Ignore = opt.Ignore
}
return &Plugin{options: options}
}
// implement the IPlugin & IPluginPreListen
// Activate ...
func (t *Plugin) Activate(container iris.IPluginContainer) error {
t.pluginContainer = container
return nil
}
// GetName ...
func (t *Plugin) GetName() string {
return Name + "[" + utils.RandomString(10) + "]" // this allows the specific plugin to be registed more than one time
}
// GetDescription TypescriptPlugin scans and compile typescript files with ease
func (t *Plugin) GetDescription() string {
return Name + " scans and compile typescript files with ease. \n"
}
// PreListen ...
func (t *Plugin) PreListen(s *iris.Iris) {
t.logger = s.Logger()
t.start()
}
//
// implementation
func (t *Plugin) start() {
defaultCompilerArgs := t.options.Tsconfig.CompilerArgs() //these will be used if no .tsconfig found.
if t.hasTypescriptFiles() {
//Can't check if permission denied returns always exists = true....
//typescriptModule := out + string(os.PathSeparator) + "typescript" + string(os.PathSeparator) + "bin"
if !npm.Exists(t.options.Bin) {
t.logger.Println("Installing typescript, please wait...")
res := npm.Install("typescript")
if res.Error != nil {
t.logger.Print(res.Error.Error())
return
}
t.logger.Print(res.Message)
}
projects := t.getTypescriptProjects()
if len(projects) > 0 {
watchedProjects := 0
//typescript project (.tsconfig) found
for _, project := range projects {
cmd := utils.CommandBuilder("node", t.options.Bin, "-p", project[0:strings.LastIndex(project, utils.PathSeparator)]) //remove the /tsconfig.json)
projectConfig := FromFile(project)
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.Println(err.Error())
return
}
}()
} else {
_, err := cmd.Output()
if err != nil {
t.logger.Println(err.Error())
return
}
}
}
t.logger.Printf("%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.options.Tsconfig.CompilerOptions.Watch {
watchedFiles = len(files)
}
//it must be always > 0 if we came here, because of if hasTypescriptFiles == true.
for _, file := range files {
cmd := utils.CommandBuilder("node", t.options.Bin)
cmd.AppendArguments(defaultCompilerArgs...)
cmd.AppendArguments(file)
_, err := cmd.Output()
cmd.Args = cmd.Args[0 : len(cmd.Args)-1] //remove the last, which is the file
if err != nil {
t.logger.Println(err.Error())
return
}
}
t.logger.Printf("%d Typescript file(s) compiled ( %d monitored by a background file watcher )", len(files), watchedFiles)
}
}
//editor activation
if len(projects) == 1 && t.options.Editor != nil {
dir := projects[0][0:strings.LastIndex(projects[0], utils.PathSeparator)]
t.options.Editor.Dir(dir)
t.pluginContainer.Add(t.options.Editor)
}
}
}
func (t *Plugin) hasTypescriptFiles() bool {
root := t.options.Dir
ignoreFolders := strings.Split(t.options.Ignore, ",")
hasTs := false
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 *Plugin) getTypescriptProjects() []string {
var projects []string
ignoreFolders := strings.Split(t.options.Ignore, ",")
root := t.options.Dir
//t.logger.Printf("\nSearching for typescript projects in %s", root)
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, utils.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 *Plugin) getTypescriptFiles() []string {
var files []string
ignoreFolders := strings.Split(t.options.Ignore, ",")
root := t.options.Dir
//t.logger.Printf("\nSearching for typescript files in %s", root)
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
}
//
//

317
render/rest/engine.go Normal file
View File

@ -0,0 +1,317 @@
package rest
import (
"bytes"
"encoding/json"
"encoding/xml"
"github.com/klauspost/compress/gzip"
"github.com/valyala/fasthttp"
)
// Engine is the generic interface for all responses.
type Engine interface {
Render(*fasthttp.RequestCtx, interface{}) error
//used only if config gzip is enabled
RenderGzip(*fasthttp.RequestCtx, interface{}) error
}
// Head defines the basic ContentType and Status fields.
type Head struct {
ContentType string
Status int
}
// Data built-in renderer.
type Data struct {
Head
}
// JSON built-in renderer.
type JSON struct {
Head
Indent bool
UnEscapeHTML bool
Prefix []byte
StreamingJSON bool
}
// JSONP built-in renderer.
type JSONP struct {
Head
Indent bool
Callback string
}
// Text built-in renderer.
type Text struct {
Head
}
// XML built-in renderer.
type XML struct {
Head
Indent bool
Prefix []byte
}
// Write outputs the header content.
func (h Head) Write(ctx *fasthttp.RequestCtx) {
ctx.Response.Header.Set(ContentType, h.ContentType)
ctx.SetStatusCode(h.Status)
}
// Render a data response.
func (d Data) Render(ctx *fasthttp.RequestCtx, v interface{}) error {
c := string(ctx.Request.Header.Peek(ContentType))
w := ctx.Response.BodyWriter()
if c != "" {
d.Head.ContentType = c
}
d.Head.Write(ctx)
w.Write(v.([]byte))
return nil
}
// RenderGzip a data response using gzip compression.
func (d Data) RenderGzip(ctx *fasthttp.RequestCtx, v interface{}) error {
c := string(ctx.Request.Header.Peek(ContentType))
if c != "" {
d.Head.ContentType = c
}
d.Head.Write(ctx)
_, err := fasthttp.WriteGzip(ctx.Response.BodyWriter(), v.([]byte))
if err == nil {
ctx.Response.Header.Add("Content-Encoding", "gzip")
}
return err
}
// Render a JSON response.
func (j JSON) Render(ctx *fasthttp.RequestCtx, v interface{}) error {
if j.StreamingJSON {
return j.renderStreamingJSON(ctx, v)
}
var result []byte
var err error
if j.Indent {
result, err = json.MarshalIndent(v, "", " ")
result = append(result, '\n')
} else {
result, err = json.Marshal(v)
}
if err != nil {
return err
}
// Unescape HTML if needed.
if j.UnEscapeHTML {
result = bytes.Replace(result, []byte("\\u003c"), []byte("<"), -1)
result = bytes.Replace(result, []byte("\\u003e"), []byte(">"), -1)
result = bytes.Replace(result, []byte("\\u0026"), []byte("&"), -1)
}
w := ctx.Response.BodyWriter()
// JSON marshaled fine, write out the result.
j.Head.Write(ctx)
if len(j.Prefix) > 0 {
w.Write(j.Prefix)
}
w.Write(result)
return nil
}
// RenderGzip a JSON response using gzip compression.
func (j JSON) RenderGzip(ctx *fasthttp.RequestCtx, v interface{}) error {
if j.StreamingJSON {
return j.renderStreamingJSONGzip(ctx, v)
}
var result []byte
var err error
if j.Indent {
result, err = json.MarshalIndent(v, "", " ")
result = append(result, '\n')
} else {
result, err = json.Marshal(v)
}
if err != nil {
return err
}
ctx.Response.Header.Add("Content-Encoding", "gzip")
// Unescape HTML if needed.
if j.UnEscapeHTML {
result = bytes.Replace(result, []byte("\\u003c"), []byte("<"), -1)
result = bytes.Replace(result, []byte("\\u003e"), []byte(">"), -1)
result = bytes.Replace(result, []byte("\\u0026"), []byte("&"), -1)
}
w := gzip.NewWriter(ctx.Response.BodyWriter())
// JSON marshaled fine, write out the result.
j.Head.Write(ctx)
if len(j.Prefix) > 0 {
w.Write(j.Prefix)
}
w.Write(result)
w.Close()
return nil
}
func (j JSON) renderStreamingJSON(ctx *fasthttp.RequestCtx, v interface{}) error {
j.Head.Write(ctx)
w := ctx.Response.BodyWriter()
if len(j.Prefix) > 0 {
w.Write(j.Prefix)
}
return json.NewEncoder(w).Encode(v)
}
func (j JSON) renderStreamingJSONGzip(ctx *fasthttp.RequestCtx, v interface{}) error {
ctx.Response.Header.Add("Content-Encoding", "gzip")
j.Head.Write(ctx)
w := gzip.NewWriter(ctx.Response.BodyWriter())
if len(j.Prefix) > 0 {
w.Write(j.Prefix)
}
w.Close()
return json.NewEncoder(w).Encode(v)
}
// Render a JSONP response.
func (j JSONP) Render(ctx *fasthttp.RequestCtx, v interface{}) error {
var result []byte
var err error
if j.Indent {
result, err = json.MarshalIndent(v, "", " ")
} else {
result, err = json.Marshal(v)
}
if err != nil {
return err
}
w := ctx.Response.BodyWriter()
// JSON marshaled fine, write out the result.
j.Head.Write(ctx)
w.Write([]byte(j.Callback + "("))
w.Write(result)
w.Write([]byte(");"))
// If indenting, append a new line.
if j.Indent {
w.Write([]byte("\n"))
}
return nil
}
// RenderGzip a JSONP response using gzip compression.
func (j JSONP) RenderGzip(ctx *fasthttp.RequestCtx, v interface{}) error {
var result []byte
var err error
if j.Indent {
result, err = json.MarshalIndent(v, "", " ")
} else {
result, err = json.Marshal(v)
}
if err != nil {
return err
}
w := gzip.NewWriter(ctx.Response.BodyWriter())
ctx.Response.Header.Add("Content-Encoding", "gzip")
// JSON marshaled fine, write out the result.
j.Head.Write(ctx)
w.Write([]byte(j.Callback + "("))
w.Write(result)
w.Write([]byte(");"))
// If indenting, append a new line.
if j.Indent {
w.Write([]byte("\n"))
}
w.Close()
return nil
}
// Render a text response.
func (t Text) Render(ctx *fasthttp.RequestCtx, v interface{}) error {
c := string(ctx.Request.Header.Peek(ContentType))
if c != "" {
t.Head.ContentType = c
}
w := ctx.Response.BodyWriter()
t.Head.Write(ctx)
w.Write([]byte(v.(string)))
return nil
}
// RenderGzip a Text response using gzip compression.
func (t Text) RenderGzip(ctx *fasthttp.RequestCtx, v interface{}) error {
c := string(ctx.Request.Header.Peek(ContentType))
if c != "" {
t.Head.ContentType = c
}
ctx.Response.Header.Add("Content-Encoding", "gzip")
t.Head.Write(ctx)
fasthttp.WriteGzip(ctx.Response.BodyWriter(), []byte(v.(string)))
return nil
}
// Render an XML response.
func (x XML) Render(ctx *fasthttp.RequestCtx, v interface{}) error {
var result []byte
var err error
if x.Indent {
result, err = xml.MarshalIndent(v, "", " ")
result = append(result, '\n')
} else {
result, err = xml.Marshal(v)
}
if err != nil {
return err
}
// XML marshaled fine, write out the result.
x.Head.Write(ctx)
w := ctx.Response.BodyWriter()
if len(x.Prefix) > 0 {
w.Write(x.Prefix)
}
w.Write(result)
return nil
}
// RenderGzip an XML response using gzip compression.
func (x XML) RenderGzip(ctx *fasthttp.RequestCtx, v interface{}) error {
var result []byte
var err error
if x.Indent {
result, err = xml.MarshalIndent(v, "", " ")
result = append(result, '\n')
} else {
result, err = xml.Marshal(v)
}
if err != nil {
return err
}
ctx.Response.Header.Add("Content-Encoding", "gzip")
// XML marshaled fine, write out the result.
x.Head.Write(ctx)
w := gzip.NewWriter(ctx.Response.BodyWriter())
if len(x.Prefix) > 0 {
w.Write(x.Prefix)
}
w.Write(result)
w.Close()
return nil
}

172
render/rest/render.go Normal file
View File

@ -0,0 +1,172 @@
package rest
import (
"github.com/kataras/iris/config"
"github.com/kataras/iris/utils"
"github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday"
"github.com/valyala/fasthttp"
)
const (
// ContentBinary header value for binary data.
ContentBinary = "application/octet-stream"
// ContentJSON header value for JSON data.
ContentJSON = "application/json"
// ContentJSONP header value for JSONP data.
ContentJSONP = "application/javascript"
// ContentLength header constant.
ContentLength = "Content-Length"
// ContentText header value for Text data.
ContentText = "text/plain"
// ContentType header constant.
ContentType = "Content-Type"
// ContentXML header value for XML data.
ContentXML = "text/xml"
)
// bufPool represents a reusable buffer pool for executing templates into.
var bufPool *utils.BufferPool
// Render is a service that provides functions for easily writing JSON, XML,
// binary data, and HTML templates out to a HTTP Response.
type Render struct {
// Customize Secure with an Options struct.
Config config.Rest
CompiledCharset string
}
// New constructs a new Render instance with the supplied configs.
func New(cfg ...config.Rest) *Render {
if bufPool == nil {
bufPool = utils.NewBufferPool(64)
}
c := config.DefaultRest().Merge(cfg)
r := &Render{
Config: c,
}
r.prepareConfig()
return r
}
func (r *Render) prepareConfig() {
// Fill in the defaults if need be.
if len(r.Config.Charset) == 0 {
r.Config.Charset = config.Charset
}
r.CompiledCharset = "; charset=" + r.Config.Charset
}
// Render is the generic function called by XML, JSON, Data, HTML, and can be called by custom implementations.
func (r *Render) Render(ctx *fasthttp.RequestCtx, e Engine, data interface{}) error {
var err error
if r.Config.Gzip {
err = e.RenderGzip(ctx, data)
} else {
err = e.Render(ctx, data)
}
if err != nil && !r.Config.DisableHTTPErrorRendering {
ctx.Response.SetBodyString(err.Error())
ctx.Response.SetStatusCode(fasthttp.StatusInternalServerError)
}
return err
}
// Data writes out the raw bytes as binary data.
func (r *Render) Data(ctx *fasthttp.RequestCtx, status int, v []byte) error {
head := Head{
ContentType: ContentBinary,
Status: status,
}
d := Data{
Head: head,
}
return r.Render(ctx, d, v)
}
// JSON marshals the given interface object and writes the JSON response.
func (r *Render) JSON(ctx *fasthttp.RequestCtx, status int, v interface{}) error {
head := Head{
ContentType: ContentJSON + r.CompiledCharset,
Status: status,
}
j := JSON{
Head: head,
Indent: r.Config.IndentJSON,
Prefix: r.Config.PrefixJSON,
UnEscapeHTML: r.Config.UnEscapeHTML,
StreamingJSON: r.Config.StreamingJSON,
}
return r.Render(ctx, j, v)
}
// JSONP marshals the given interface object and writes the JSON response.
func (r *Render) JSONP(ctx *fasthttp.RequestCtx, status int, callback string, v interface{}) error {
head := Head{
ContentType: ContentJSONP + r.CompiledCharset,
Status: status,
}
j := JSONP{
Head: head,
Indent: r.Config.IndentJSON,
Callback: callback,
}
return r.Render(ctx, j, v)
}
// Text writes out a string as plain text.
func (r *Render) Text(ctx *fasthttp.RequestCtx, status int, v string) error {
head := Head{
ContentType: ContentText + r.CompiledCharset,
Status: status,
}
t := Text{
Head: head,
}
return r.Render(ctx, t, v)
}
// XML marshals the given interface object and writes the XML response.
func (r *Render) XML(ctx *fasthttp.RequestCtx, status int, v interface{}) error {
head := Head{
ContentType: ContentXML + r.CompiledCharset,
Status: status,
}
x := XML{
Head: head,
Indent: r.Config.IndentXML,
Prefix: r.Config.PrefixXML,
}
return r.Render(ctx, x, v)
}
// Markdown parses and returns the converted html from a markdown []byte
// accepts two parameters
// first is the http status code
// second is the markdown string
//
// Note that: Works different than the other rest's functions.
func (r *Render) Markdown(markdownBytes []byte) string {
buf := blackfriday.MarkdownCommon(markdownBytes)
if r.Config.MarkdownSanitize {
buf = bluemonday.UGCPolicy().SanitizeBytes(buf)
}
return string(buf)
}

View File

@ -0,0 +1,8 @@
# Folder Information
This folder contains the template support for Iris. The folder name is singular (template) so the `/template/engine`, because you can use **ONLY ONE** at the same time.
## How to use
**Refer to the Book**

View File

@ -0,0 +1,76 @@
package amber
import (
"html/template"
"fmt"
"io"
"path/filepath"
"sync"
"github.com/eknkc/amber"
"github.com/kataras/iris/config"
)
type Engine struct {
Config *config.Template
templateCache map[string]*template.Template
mu sync.Mutex
}
func New(cfg config.Template) *Engine {
return &Engine{Config: &cfg}
}
func (e *Engine) BuildTemplates() error {
opt := amber.DirOptions{}
opt.Recursive = true
if e.Config.Extensions == nil || len(e.Config.Extensions) == 0 {
e.Config.Extensions = []string{".html"}
}
// prepare the global amber funcs
funcs := template.FuncMap{}
for k, v := range amber.FuncMap { // add the amber's default funcs
funcs[k] = v
}
if e.Config.Amber.Funcs != nil { // add the config's funcs
for k, v := range e.Config.Amber.Funcs {
funcs[k] = v
}
}
amber.FuncMap = funcs //set the funcs
opt.Ext = e.Config.Extensions[0]
templates, err := amber.CompileDir(e.Config.Directory, opt, amber.DefaultOptions) // this returns the map with stripped extension, we want extension so we copy the map
if err == nil {
e.templateCache = make(map[string]*template.Template)
for k, v := range templates {
name := filepath.ToSlash(k + opt.Ext)
e.templateCache[name] = v
delete(templates, k)
}
}
return err
}
func (e *Engine) fromCache(relativeName string) *template.Template {
e.mu.Lock()
tmpl, ok := e.templateCache[relativeName]
if ok {
e.mu.Unlock()
return tmpl
}
e.mu.Unlock()
return nil
}
func (e *Engine) ExecuteWriter(out io.Writer, name string, binding interface{}, layout string) error {
if tmpl := e.fromCache(name); tmpl != nil {
return tmpl.ExecuteTemplate(out, name, binding)
}
return fmt.Errorf("[IRIS TEMPLATES] Template with name %s doesn't exists in the dir %s", name, e.Config.Directory)
}

View File

@ -0,0 +1,227 @@
package html
import (
"bytes"
"fmt"
"html/template"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/kataras/iris/config"
)
type (
Engine struct {
Config *config.Template
Templates *template.Template
// Middleware
// Note:
// I see that many template engines returns html/template as result
// so I decided that the HTMLTemplate should accept a middleware for the final string content will be parsed to the main *html/template.Template
// for example user of this property is Jade, currently
Middleware func(string, string) (string, error)
}
)
var emptyFuncs = template.FuncMap{
"yield": func() (string, error) {
return "", fmt.Errorf("yield was called, yet no layout defined")
},
"partial": func() (string, error) {
return "", fmt.Errorf("block was called, yet no layout defined")
},
"current": func() (string, error) {
return "", nil
}, "render": func() (string, error) {
return "", nil
},
// just for test with jade
/*"bold": func() (string, error) {
return "", nil
},*/
}
// New creates and returns the HTMLTemplate template engine
func New(c config.Template) *Engine {
return &Engine{Config: &c}
}
func (s *Engine) BuildTemplates() error {
if s.Config.Asset == nil || s.Config.AssetNames == nil {
return s.buildFromDir()
}
return s.buildFromAsset()
}
func (s *Engine) buildFromDir() error {
if s.Config.Directory == "" {
return nil //we don't return fill error here(yet)
}
var templateErr error
/*var minifier *minify.M
if s.Config.Minify {
minifier = minify.New()
minifier.AddFunc("text/html", htmlMinifier.Minify)
} // Note: minifier has bugs, I complety remove this from Iris.
*/
dir := s.Config.Directory
s.Templates = template.New(dir)
s.Templates.Delims(s.Config.HTMLTemplate.Left, s.Config.HTMLTemplate.Right)
hasMiddleware := s.Middleware != nil
// Walk the supplied directory and compile any files that match our extension list.
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if info == nil || info.IsDir() {
return nil
}
rel, err := filepath.Rel(dir, path)
if err != nil {
return err
}
ext := ""
if strings.Index(rel, ".") != -1 {
ext = filepath.Ext(rel)
}
for _, extension := range s.Config.Extensions {
if ext == extension {
buf, err := ioutil.ReadFile(path)
contents := string(buf)
/*if s.Config.Minify {
buf, err = minifier.Bytes("text/html", buf)
}*/
if err != nil {
templateErr = err
break
}
name := filepath.ToSlash(rel)
tmpl := s.Templates.New(name)
if hasMiddleware {
contents, err = s.Middleware(name, contents)
}
if err != nil {
templateErr = err
break
}
// Add our funcmaps.
if s.Config.HTMLTemplate.Funcs != nil {
tmpl.Funcs(s.Config.HTMLTemplate.Funcs)
}
tmpl.Funcs(emptyFuncs).Parse(contents)
break
}
}
return nil
})
return templateErr
}
func (s *Engine) buildFromAsset() error {
var templateErr error
dir := s.Config.Directory
s.Templates = template.New(dir)
s.Templates.Delims(s.Config.HTMLTemplate.Left, s.Config.HTMLTemplate.Right)
for _, path := range s.Config.AssetNames() {
if !strings.HasPrefix(path, dir) {
continue
}
rel, err := filepath.Rel(dir, path)
if err != nil {
panic(err)
}
ext := ""
if strings.Index(rel, ".") != -1 {
ext = "." + strings.Join(strings.Split(rel, ".")[1:], ".")
}
for _, extension := range s.Config.Extensions {
if ext == extension {
buf, err := s.Config.Asset(path)
if err != nil {
panic(err)
}
name := filepath.ToSlash(rel)
tmpl := s.Templates.New(name)
// Add our funcmaps.
//for _, funcs := range s.Config.HTMLTemplate.Funcs {
if s.Config.HTMLTemplate.Funcs != nil {
tmpl.Funcs(s.Config.HTMLTemplate.Funcs)
}
tmpl.Funcs(emptyFuncs).Parse(string(buf))
break
}
}
}
return templateErr
}
func (s *Engine) executeTemplateBuf(name string, binding interface{}) (*bytes.Buffer, error) {
buf := new(bytes.Buffer)
err := s.Templates.ExecuteTemplate(buf, name, binding)
return buf, err
}
func (s *Engine) layoutFuncsFor(name string, binding interface{}) {
funcs := template.FuncMap{
"yield": func() (template.HTML, error) {
buf, err := s.executeTemplateBuf(name, binding)
// Return safe HTML here since we are rendering our own template.
return template.HTML(buf.String()), err
},
"current": func() (string, error) {
return name, nil
},
"partial": func(partialName string) (template.HTML, error) {
fullPartialName := fmt.Sprintf("%s-%s", partialName, name)
if s.Config.HTMLTemplate.RequirePartials || s.Templates.Lookup(fullPartialName) != nil {
buf, err := s.executeTemplateBuf(fullPartialName, binding)
// Return safe HTML here since we are rendering our own template.
return template.HTML(buf.String()), err
}
return "", nil
},
"render": func(fullPartialName string) (template.HTML, error) {
buf, err := s.executeTemplateBuf(fullPartialName, binding)
// Return safe HTML here since we are rendering our own template.
return template.HTML(buf.String()), err
},
// just for test with jade
/*"bold": func(content string) (template.HTML, error) {
return template.HTML("<b>" + content + "</b>"), nil
},*/
}
if tpl := s.Templates.Lookup(name); tpl != nil {
tpl.Funcs(funcs)
}
}
func (s *Engine) ExecuteWriter(out io.Writer, name string, binding interface{}, layout string) error {
if layout != "" && layout != config.NoLayout {
s.layoutFuncsFor(name, binding)
name = layout
}
return s.Templates.ExecuteTemplate(out, name, binding)
}

View File

@ -0,0 +1,20 @@
package jade
import (
"github.com/Joker/jade"
"github.com/kataras/iris/config"
"github.com/kataras/iris/render/template/engine/html"
)
type Engine struct {
*html.Engine
}
func New(cfg config.Template) *Engine {
underline := &Engine{Engine: html.New(cfg)}
underline.Middleware = func(relativeName string, fileContents string) (string, error) {
return jade.Parse(relativeName, fileContents)
}
return underline
}

View File

@ -0,0 +1,151 @@
package markdown
import (
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"fmt"
"github.com/kataras/iris/config"
"github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday"
)
// Supports RAW markdown only, no context binding or layout, to use dynamic markdown with other template engine use the context.Markdown/MarkdownString
type (
Engine struct {
Config *config.Template
templateCache map[string][]byte
mu sync.Mutex
}
)
// New creates and returns a Pongo template engine
func New(c config.Template) *Engine {
return &Engine{Config: &c, templateCache: make(map[string][]byte)}
}
func (e *Engine) BuildTemplates() error {
if e.Config.Asset == nil || e.Config.AssetNames == nil {
return e.buildFromDir()
}
return e.buildFromAsset()
}
func (e *Engine) buildFromDir() (templateErr error) {
if e.Config.Directory == "" {
return nil //we don't return fill error here(yet)
}
dir := e.Config.Directory
// Walk the supplied directory and compile any files that match our extension list.
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if info == nil || info.IsDir() {
return nil
}
rel, err := filepath.Rel(dir, path)
if err != nil {
return err
}
ext := ""
if strings.Index(rel, ".") != -1 {
ext = filepath.Ext(rel)
}
for _, extension := range e.Config.Extensions {
if ext == extension {
buf, err := ioutil.ReadFile(path)
if err != nil {
templateErr = err
break
}
buf = blackfriday.MarkdownCommon(buf)
if e.Config.Markdown.Sanitize {
buf = bluemonday.UGCPolicy().SanitizeBytes(buf)
}
if err != nil {
templateErr = err
break
}
name := filepath.ToSlash(rel)
e.templateCache[name] = buf
break
}
}
return nil
})
return nil
}
func (e *Engine) buildFromAsset() error {
var templateErr error
dir := e.Config.Directory
for _, path := range e.Config.AssetNames() {
if !strings.HasPrefix(path, dir) {
continue
}
rel, err := filepath.Rel(dir, path)
if err != nil {
panic(err)
}
ext := ""
if strings.Index(rel, ".") != -1 {
ext = "." + strings.Join(strings.Split(rel, ".")[1:], ".")
}
for _, extension := range e.Config.Extensions {
if ext == extension {
buf, err := e.Config.Asset(path)
if err != nil {
templateErr = err
break
}
b := blackfriday.MarkdownCommon(buf)
if e.Config.Markdown.Sanitize {
b = bluemonday.UGCPolicy().SanitizeBytes(b)
}
name := filepath.ToSlash(rel)
e.templateCache[name] = b
break
}
}
}
return templateErr
}
func (e *Engine) fromCache(relativeName string) []byte {
e.mu.Lock()
tmpl, ok := e.templateCache[relativeName]
if ok {
e.mu.Unlock() // defer is slow
return tmpl
}
e.mu.Unlock() // defer is slow
return nil
}
// layout here is unnesecery
func (e *Engine) ExecuteWriter(out io.Writer, name string, binding interface{}, layout string) error {
if tmpl := e.fromCache(name); tmpl != nil {
_, err := out.Write(tmpl)
return err
}
return fmt.Errorf("[IRIS TEMPLATES] Template with name %s doesn't exists in the dir %s", name, e.Config.Directory)
}

View File

@ -0,0 +1,189 @@
package pongo
/* TODO:
1. Find if pongo2 supports layout, it should have extends or something like django but I don't know yet, if exists then do something with the layour parameter in Exeucte/Gzip.
*/
import (
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"fmt"
"github.com/flosch/pongo2"
"github.com/kataras/iris/config"
)
type (
Engine struct {
Config *config.Template
templateCache map[string]*pongo2.Template
mu sync.Mutex
}
)
// New creates and returns a Pongo template engine
func New(c config.Template) *Engine {
return &Engine{Config: &c, templateCache: make(map[string]*pongo2.Template)}
}
func (p *Engine) BuildTemplates() error {
// Add our filters. first
for k, v := range p.Config.Pongo.Filters {
pongo2.RegisterFilter(k, v)
}
if p.Config.Asset == nil || p.Config.AssetNames == nil {
return p.buildFromDir()
}
return p.buildFromAsset()
}
func (p *Engine) buildFromDir() (templateErr error) {
if p.Config.Directory == "" {
return nil //we don't return fill error here(yet)
}
dir := p.Config.Directory
fsLoader, err := pongo2.NewLocalFileSystemLoader(dir) // I see that this doesn't read the content if already parsed, so do it manually via filepath.Walk
if err != nil {
return err
}
set := pongo2.NewSet("", fsLoader)
set.Globals = getPongoContext(p.Config.Pongo.Globals)
// Walk the supplied directory and compile any files that match our extension list.
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
// Fix same-extension-dirs bug: some dir might be named to: "users.tmpl", "local.html".
// These dirs should be excluded as they are not valid golang templates, but files under
// them should be treat as normal.
// If is a dir, return immediately (dir is not a valid golang template).
if info == nil || info.IsDir() {
return nil
}
rel, err := filepath.Rel(dir, path)
if err != nil {
return err
}
ext := ""
if strings.Index(rel, ".") != -1 {
ext = filepath.Ext(rel)
}
for _, extension := range p.Config.Extensions {
if ext == extension {
buf, err := ioutil.ReadFile(path)
if err != nil {
templateErr = err
break
}
if err != nil {
templateErr = err
break
}
name := filepath.ToSlash(rel)
p.templateCache[name], templateErr = set.FromString(string(buf))
//_, templateErr = p.Templates.FromCache(rel) // use Relative, no from path because it calculates the basedir of the fsLoader
if templateErr != nil {
return templateErr // break the file walk(;)
}
break
}
}
return nil
})
return
}
func (p *Engine) buildFromAsset() error {
var templateErr error
dir := p.Config.Directory
fsLoader, err := pongo2.NewLocalFileSystemLoader(dir)
if err != nil {
return err
}
set := pongo2.NewSet("", fsLoader)
set.Globals = getPongoContext(p.Config.Pongo.Globals)
for _, path := range p.Config.AssetNames() {
if !strings.HasPrefix(path, dir) {
continue
}
rel, err := filepath.Rel(dir, path)
if err != nil {
panic(err)
}
ext := ""
if strings.Index(rel, ".") != -1 {
ext = "." + strings.Join(strings.Split(rel, ".")[1:], ".")
}
for _, extension := range p.Config.Extensions {
if ext == extension {
buf, err := p.Config.Asset(path)
if err != nil {
templateErr = err
break
}
name := filepath.ToSlash(rel)
p.templateCache[name], err = set.FromString(string(buf)) // I don't konw if that will work, yet
if err != nil {
templateErr = err
break
}
break
}
}
}
return templateErr
}
// getPongoContext returns the pongo2.Context from map[string]interface{} or from pongo2.Context, used internaly
func getPongoContext(templateData interface{}) pongo2.Context {
if templateData == nil {
return nil
}
if v, isMap := templateData.(map[string]interface{}); isMap {
return v
}
if contextData, isPongoContext := templateData.(pongo2.Context); isPongoContext {
return contextData
}
return nil
}
func (p *Engine) fromCache(relativeName string) *pongo2.Template {
p.mu.Lock()
tmpl, ok := p.templateCache[relativeName]
if ok {
p.mu.Unlock() // defer is slow
return tmpl
}
p.mu.Unlock() // defer is slow
return nil
}
// layout here is unnesecery
func (p *Engine) ExecuteWriter(out io.Writer, name string, binding interface{}, layout string) error {
if tmpl := p.fromCache(name); tmpl != nil {
return tmpl.ExecuteWriter(getPongoContext(binding), out)
}
return fmt.Errorf("[IRIS TEMPLATES] Template with name %s doesn't exists in the dir %s", name, p.Config.Directory)
}

154
render/template/template.go Normal file
View File

@ -0,0 +1,154 @@
package template
import (
"fmt"
"io"
"github.com/klauspost/compress/gzip"
"sync"
"github.com/kataras/iris/config"
"github.com/kataras/iris/context"
"github.com/kataras/iris/render/template/engine/amber"
"github.com/kataras/iris/render/template/engine/html"
"github.com/kataras/iris/render/template/engine/jade"
"github.com/kataras/iris/render/template/engine/markdown"
"github.com/kataras/iris/render/template/engine/pongo"
"github.com/kataras/iris/utils"
)
type (
Engine interface {
BuildTemplates() error
ExecuteWriter(out io.Writer, name string, binding interface{}, layout string) error
}
Template struct {
Engine Engine
IsDevelopment bool
Gzip bool
ContentType string
Layout string
buffer *utils.BufferPool // this is used only for RenderString
gzipWriterPool sync.Pool
}
)
// New creates and returns a Template instance which keeps the Template Engine and helps with render
func New(c config.Template) *Template {
var e Engine
// [ENGINE-2]
switch c.Engine {
case config.HTMLEngine:
e = html.New(c) // HTMLTemplate
case config.PongoEngine:
e = pongo.New(c) // Pongo2
case config.MarkdownEngine:
e = markdown.New(c) // Markdown
case config.JadeEngine:
e = jade.New(c) // Jade
case config.AmberEngine:
e = amber.New(c) // Amber
default: // config.NoEngine
return nil
}
if err := e.BuildTemplates(); err != nil { // first build the templates, if error then panic because this is called before server's run
panic(err)
}
compiledContentType := c.ContentType + "; charset=" + c.Charset
t := &Template{
Engine: e,
IsDevelopment: c.IsDevelopment,
Gzip: c.Gzip,
ContentType: compiledContentType,
Layout: c.Layout,
buffer: utils.NewBufferPool(64),
gzipWriterPool: sync.Pool{New: func() interface{} {
return &gzip.Writer{}
}},
}
return t
}
func (t *Template) Render(ctx context.IContext, name string, binding interface{}, layout ...string) (err error) {
if t == nil { // No engine was given but .Render was called
ctx.WriteHTML(403, "<b> Iris </b> <br/> Templates are disabled via config.NoEngine, check your iris' configuration please.")
return fmt.Errorf("[IRIS TEMPLATES] Templates are disabled via config.NoEngine, check your iris' configuration please.\n")
}
// build templates again on each render if IsDevelopment.
if t.IsDevelopment {
if err = t.Engine.BuildTemplates(); err != nil {
return
}
}
// I don't like this, something feels wrong
_layout := ""
if len(layout) > 0 {
_layout = layout[0]
}
if _layout == "" {
_layout = t.Layout
}
//
ctx.GetRequestCtx().Response.Header.Set("Content-Type", t.ContentType)
var out io.Writer
if t.Gzip {
ctx.GetRequestCtx().Response.Header.Add("Content-Encoding", "gzip")
gzipWriter := t.gzipWriterPool.Get().(*gzip.Writer)
gzipWriter.Reset(ctx.GetRequestCtx().Response.BodyWriter())
defer gzipWriter.Close()
defer t.gzipWriterPool.Put(gzipWriter)
out = gzipWriter
} else {
out = ctx.GetRequestCtx().Response.BodyWriter()
}
err = t.Engine.ExecuteWriter(out, name, binding, _layout)
return
}
func (t *Template) RenderString(name string, binding interface{}, layout ...string) (result string, err error) {
if t == nil { // No engine was given but .Render was called
err = fmt.Errorf("[IRIS TEMPLATES] Templates are disabled via config.NoEngine, check your iris' configuration please.\n")
return
}
// build templates again on each render if IsDevelopment.
if t.IsDevelopment {
if err = t.Engine.BuildTemplates(); err != nil {
return
}
}
// I don't like this, something feels wrong
_layout := ""
if len(layout) > 0 {
_layout = layout[0]
}
if _layout == "" {
_layout = t.Layout
}
out := t.buffer.Get()
// if we have problems later consider that -> out.Reset()
defer t.buffer.Put(out)
err = t.Engine.ExecuteWriter(out, name, binding, _layout)
if err == nil {
result = out.String()
}
return
}

102
route.go Normal file
View File

@ -0,0 +1,102 @@
package iris
import (
"strings"
)
type (
// IRoute is the interface which the Route should implements
// it useful to have it as an interface because this interface is passed to the plugins
IRoute interface {
GetMethod() string
GetDomain() string
GetPath() string
GetMiddleware() Middleware
HasCors() bool
}
// Route contains basic and temporary info about the route in order to be stored to the tree
// It's struct because we pass it ( as IRoute) to the plugins
Route struct {
method string
domain string
fullpath string
middleware Middleware
}
)
var _ IRoute = &Route{}
// NewRoute creates, from a path string, and a slice of HandlerFunc
func NewRoute(method string, registedPath string, middleware Middleware) *Route {
domain := ""
//dirdy but I'm not touching this again:P
if registedPath[0] != SlashByte && strings.Contains(registedPath, ".") && (strings.IndexByte(registedPath, SlashByte) == -1 || strings.IndexByte(registedPath, SlashByte) > strings.IndexByte(registedPath, '.')) {
//means that is a path with domain
//we have to extract the domain
//find the first '/'
firstSlashIndex := strings.IndexByte(registedPath, SlashByte)
//firt of all remove the first '/' if that exists and we have domain
if firstSlashIndex == 0 {
//e.g /admin.ideopod.com/hey
//then just remove the first slash and re-execute the NewRoute and return it
registedPath = registedPath[1:]
return NewRoute(method, registedPath, middleware)
}
//if it's just the domain, then set it(registedPath) as the domain
//and after set the registedPath to a slash '/' for the path part
if firstSlashIndex == -1 {
domain = registedPath
registedPath = Slash
} else {
//we have a domain + path
domain = registedPath[0:firstSlashIndex]
registedPath = registedPath[len(domain):]
}
}
r := &Route{method: method, domain: domain, fullpath: registedPath, middleware: middleware}
return r
}
// GetMethod returns the http method
func (r Route) GetMethod() string {
return r.method
}
// GetDomain returns the registed domain which this route is ( if none, is "" which is means "localhost"/127.0.0.1)
func (r Route) GetDomain() string {
return r.domain
}
// GetPath returns the full registed path
func (r Route) GetPath() string {
return r.fullpath
}
// GetMiddleware returns the chain of the []HandlerFunc registed to this Route
func (r Route) GetMiddleware() Middleware {
return r.middleware
}
// HasCors check if middleware passsed to a route has cors
func (r *Route) HasCors() bool {
return RouteConflicts(r, "httpmethod")
}
// RouteConflicts checks for route's middleware conflicts
func RouteConflicts(r *Route, with string) bool {
for _, h := range r.middleware {
if m, ok := h.(interface {
Conflicts() string
}); ok {
if c := m.Conflicts(); c == with {
return true
}
}
}
return false
}

258
router.go Normal file
View File

@ -0,0 +1,258 @@
package iris
import (
"net/http/pprof"
"strings"
"sync"
"github.com/kataras/iris/utils"
"github.com/valyala/fasthttp"
)
const (
// ParameterStartByte is very used on the node, it's just contains the byte for the ':' rune/char
ParameterStartByte = byte(':')
// SlashByte is just a byte of '/' rune/char
SlashByte = byte('/')
// Slash is just a string of "/"
Slash = "/"
// MatchEverythingByte is just a byte of '*" rune/char
MatchEverythingByte = byte('*')
// HTTP Methods(1)
// MethodGet "GET"
MethodGet = "GET"
// MethodPost "POST"
MethodPost = "POST"
// MethodPut "PUT"
MethodPut = "PUT"
// MethodDelete "DELETE"
MethodDelete = "DELETE"
// MethodConnect "CONNECT"
MethodConnect = "CONNECT"
// MethodHead "HEAD"
MethodHead = "HEAD"
// MethodPatch "PATCH"
MethodPatch = "PATCH"
// MethodOptions "OPTIONS"
MethodOptions = "OPTIONS"
// MethodTrace "TRACE"
MethodTrace = "TRACE"
)
var (
// HTTP Methods(2)
// MethodConnectBytes []byte("CONNECT")
MethodConnectBytes = []byte(MethodConnect)
// AllMethods "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", "PATCH", "OPTIONS", "TRACE"
AllMethods = [...]string{"GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", "PATCH", "OPTIONS", "TRACE"}
)
// router internal is the route serving service, one router per server
type router struct {
*GardenParty
*HTTPErrorContainer
station *Iris
garden *Garden
methodMatch func(m1, m2 string) bool
getRequestPath func(*fasthttp.RequestCtx) []byte
ServeRequest func(reqCtx *fasthttp.RequestCtx)
// errorPool is responsible to get the Context to handle not found errors
errorPool sync.Pool
//it's true when optimize already ran
optimized bool
mu sync.Mutex
}
// methodMatchCorsFunc is sets the methodMatch when cors enabled (look router.optimize), it's allowing OPTIONS method to all other methods except GET
func methodMatchCorsFunc(m1, reqMethod string) bool {
return m1 == reqMethod || reqMethod == MethodOptions //(m1 != MethodGet && reqMethod == MethodOptions)
}
// methodMatchFunc for normal method match
func methodMatchFunc(m1, m2 string) bool {
return m1 == m2
}
func getRequestPathDefault(reqCtx *fasthttp.RequestCtx) []byte {
// default to escape then
return reqCtx.Path()
}
// newRouter creates and returns an empty router
func newRouter(station *Iris) *router {
r := &router{
station: station,
garden: &Garden{},
methodMatch: methodMatchFunc,
getRequestPath: getRequestPathDefault,
HTTPErrorContainer: defaultHTTPErrors(),
GardenParty: &GardenParty{relativePath: "/", station: station, root: true},
errorPool: station.newContextPool()}
r.ServeRequest = r.serveFunc
return r
}
// addRoute calls the Plant, is created to set the router's station
func (r *router) addRoute(route IRoute) {
r.mu.Lock()
defer r.mu.Unlock()
r.garden.Plant(r.station, route)
}
//check if any tree has cors setted to true, means that cors middleware is added
func (r *router) cors() (has bool) {
r.garden.visitAll(func(i int, tree *tree) {
if tree.cors {
has = true
}
})
return
}
// check if any tree has subdomains
func (r *router) hosts() (has bool) {
r.garden.visitAll(func(i int, tree *tree) {
if tree.hosts {
has = true
}
})
return
}
// optimize runs once before listen, it checks if cors or hosts enabled and make the necessary changes to the Router itself
func (r *router) optimize() {
if r.optimized {
return
}
if r.cors() {
r.methodMatch = methodMatchCorsFunc
}
// For performance only,in order to not check at runtime for hosts and subdomains, I think it's better to do this:
if r.hosts() {
r.ServeRequest = r.serveDomainFunc
}
//if PathEscape disabled, then take the raw URI
if r.station.config.DisablePathEscape {
r.getRequestPath = func(reqCtx *fasthttp.RequestCtx) []byte {
// RequestURI fixes the https://github.com/kataras/iris/issues/135
return reqCtx.RequestURI()
}
}
// set the debug profiling handlers if Profile enabled, before the server startup, not earlier
if r.station.config.Profile && r.station.config.ProfilePath != "" {
debugPath := r.station.config.ProfilePath
htmlMiddleware := func(ctx *Context) {
ctx.SetContentType(ContentHTML + r.station.rest.CompiledCharset)
ctx.Next()
}
indexHandler := ToHandlerFunc(pprof.Index)
cmdlineHandler := ToHandlerFunc(pprof.Cmdline)
profileHandler := ToHandlerFunc(pprof.Profile)
symbolHandler := ToHandlerFunc(pprof.Symbol)
goroutineHandler := ToHandlerFunc(pprof.Handler("goroutine"))
heapHandler := ToHandlerFunc(pprof.Handler("heap"))
threadcreateHandler := ToHandlerFunc(pprof.Handler("threadcreate"))
debugBlockHandler := ToHandlerFunc(pprof.Handler("block"))
r.Get(debugPath+"/*action", htmlMiddleware, func(ctx *Context) {
action := ctx.Param("action")
if len(action) > 1 {
if strings.Contains(action, "cmdline") {
cmdlineHandler.Serve((ctx))
} else if strings.Contains(action, "profile") {
profileHandler.Serve(ctx)
} else if strings.Contains(action, "symbol") {
symbolHandler.Serve(ctx)
} else if strings.Contains(action, "goroutine") {
goroutineHandler.Serve(ctx)
} else if strings.Contains(action, "heap") {
heapHandler.Serve(ctx)
} else if strings.Contains(action, "threadcreate") {
threadcreateHandler.Serve(ctx)
} else if strings.Contains(action, "debug/block") {
debugBlockHandler.Serve(ctx)
}
} else {
indexHandler.Serve(ctx)
}
})
}
r.optimized = true
}
// notFound internal method, it justs takes the context from pool ( in order to have the custom errors available) and procedure a Not Found 404 error
// this is being called when no route was found used on the ServeRequest.
func (r *router) notFound(reqCtx *fasthttp.RequestCtx) {
ctx := r.errorPool.Get().(*Context)
ctx.Reset(reqCtx)
ctx.NotFound()
r.errorPool.Put(ctx)
}
//************************************************************************************
// serveFunc & serveDomainFunc selected on router.optimize, which runs before station's listen
// they are not used directly.
//************************************************************************************
// serve finds and serves a route by it's request context
// If no route found, it sends an http status 404
func (r *router) serveFunc(reqCtx *fasthttp.RequestCtx) {
method := utils.BytesToString(reqCtx.Method())
tree := r.garden.first
path := utils.BytesToString(r.getRequestPath(reqCtx))
for tree != nil {
if r.methodMatch(tree.method, method) {
if !tree.serve(reqCtx, path) {
r.notFound(reqCtx)
}
return
}
tree = tree.next
}
//not found, get the first's pool and use that to send a custom http error(if setted)
r.notFound(reqCtx)
}
// serveDomainFunc finds and serves a domain tree's route by it's request context
// If no route found, it sends an http status 404
func (r *router) serveDomainFunc(reqCtx *fasthttp.RequestCtx) {
method := utils.BytesToString(reqCtx.Method())
domain := utils.BytesToString(reqCtx.Host())
path := r.getRequestPath(reqCtx)
tree := r.garden.first
for tree != nil {
if tree.hosts && tree.domain == domain {
// here we have an issue, at fasthttp/uri.go 273-274 line normalize path it adds a '/' slash at the beginning, it doesn't checks for subdomains
// I could fix it but i leave it as it is, I just create a new function inside tree named 'serveReturn' which accepts a path too. ->
//-> reqCtx.Request.URI().SetPathBytes(append(reqCtx.Host(), reqCtx.Path()...)) <-
path = append(reqCtx.Host(), path...)
}
if r.methodMatch(tree.method, method) {
if tree.serve(reqCtx, utils.BytesToString(path)) {
return
}
}
tree = tree.next
}
//not found, get the first's pool and use that to send a custom http error(if setted)
r.notFound(reqCtx)
}

6
server/README.md Normal file
View File

@ -0,0 +1,6 @@
## Package information
I decide to split the whole server from the main iris package because these files don't depends on any of the iris' types.
**That's it.**

26
server/errors.go Normal file
View File

@ -0,0 +1,26 @@
package server
import "github.com/kataras/iris/errors"
var (
// ErrServerPortAlreadyUsed returns an error with message: 'Server can't run, port is already used'
ErrServerPortAlreadyUsed = errors.New("Server can't run, port is already used")
// ErrServerAlreadyStarted returns an error with message: 'Server is already started and listening'
ErrServerAlreadyStarted = errors.New("Server is already started and listening")
// ErrServerOptionsMissing returns an error with message: 'You have to pass iris.ServerOptions'
ErrServerOptionsMissing = errors.New("You have to pass iris.ServerOptions")
// ErrServerTLSOptionsMissing returns an error with message: 'You have to set CertFile and KeyFile to iris.ServerOptions before ListenTLS'
ErrServerTLSOptionsMissing = errors.New("You have to set CertFile and KeyFile to iris.ServerOptions before ListenTLS")
// ErrServerIsClosed returns an error with message: 'Can't close the server, propably is already closed or never started'
ErrServerIsClosed = errors.New("Can't close the server, propably is already closed or never started")
// ErrServerUnknown returns an error with message: 'Unknown reason from Server, please report this as bug!'
ErrServerUnknown = errors.New("Unknown reason from Server, please report this as bug!")
// ErrParsedAddr returns an error with message: 'ListeningAddr error, for TCP and UDP, the syntax of ListeningAddr is host:port, like 127.0.0.1:8080.
// If host is omitted, as in :8080, Listen listens on all available interfaces instead of just the interface with the given host address.
// See Dial for more details about address syntax'
ErrParsedAddr = errors.New("ListeningAddr error, for TCP and UDP, the syntax of ListeningAddr is host:port, like 127.0.0.1:8080. If host is omitted, as in :8080, Listen listens on all available interfaces instead of just the interface with the given host address. See Dial for more details about address syntax")
// ErrServerRemoveUnix returns an error with message: 'Unexpected error when trying to remove unix socket file +filename: +specific error"'
ErrServerRemoveUnix = errors.New("Unexpected error when trying to remove unix socket file. Addr: %s | Trace: %s")
// ErrServerChmod returns an error with message: 'Cannot chmod +mode for +host:+specific error
ErrServerChmod = errors.New("Cannot chmod %#o for %q: %s")
)

204
server/server.go Normal file
View File

@ -0,0 +1,204 @@
package server
import (
"net"
"os"
"strings"
"github.com/kataras/iris/config"
"github.com/valyala/fasthttp"
)
// Server is the IServer's implementation, holds the fasthttp's Server, a net.Listener, the ServerOptions, and the handler
// handler is registed at the Station/Iris level
type Server struct {
*fasthttp.Server
listener net.Listener
Config config.Server
started bool
tls bool
handler fasthttp.RequestHandler
}
// New returns a pointer to a Server object, and set it's options if any, nothing more
func New(cfg ...config.Server) *Server {
c := config.DefaultServer().Merge(cfg)
s := &Server{Server: &fasthttp.Server{Name: config.ServerName}, Config: c}
s.Config.ListeningAddr = parseAddr(s.Config.ListeningAddr)
return s
}
// SetHandler sets the handler in order to listen on new requests, this is done at the Station/Iris level
func (s *Server) SetHandler(h fasthttp.RequestHandler) {
s.handler = h
if s.Server != nil {
s.Server.Handler = s.handler
}
}
// Handler returns the fasthttp.RequestHandler which is registed to the Server
func (s *Server) Handler() fasthttp.RequestHandler {
return s.handler
}
// IsListening returns true if server is listening/started, otherwise false
func (s *Server) IsListening() bool {
return s.started
}
// IsSecure returns true if server uses TLS, otherwise false
func (s *Server) IsSecure() bool {
return s.tls
}
// Listener returns the net.Listener which this server (is) listening to
func (s *Server) Listener() net.Listener {
return s.listener
}
//Serve just serves a listener, it is a blocking action, plugin.PostListen is not fired here.
func (s *Server) Serve(l net.Listener) error {
s.listener = l
return s.Server.Serve(l)
}
// listen starts the process of listening to the new requests
func (s *Server) listen() (err error) {
if s.started {
err = ErrServerAlreadyStarted.Return()
return
}
s.listener, err = net.Listen("tcp4", s.Config.ListeningAddr)
if err != nil {
err = ErrServerPortAlreadyUsed.Return()
return
}
//Non-block way here because I want the plugin's PostListen ability...
go s.Server.Serve(s.listener)
s.started = true
s.tls = false
return
}
// listenTLS starts the process of listening to the new requests using TLS, keyfile and certfile are given before this method fires
func (s *Server) listenTLS() (err error) {
if s.started {
err = ErrServerAlreadyStarted.Return()
return
}
if s.Config.CertFile == "" || s.Config.KeyFile == "" {
err = ErrServerTLSOptionsMissing.Return()
return
}
s.listener, err = net.Listen("tcp4", s.Config.ListeningAddr)
if err != nil {
err = ErrServerPortAlreadyUsed.Return()
return
}
go s.Server.ServeTLS(s.listener, s.Config.CertFile, s.Config.KeyFile)
s.started = true
s.tls = true
return
}
// listenUnix starts the process of listening to the new requests using a 'socket file', this works only on unix
func (s *Server) listenUnix() (err error) {
if s.started {
err = ErrServerAlreadyStarted.Return()
return
}
mode := s.Config.Mode
//this code is from fasthttp ListenAndServeUNIX, I extracted it because we need the tcp.Listener
if errOs := os.Remove(s.Config.ListeningAddr); errOs != nil && !os.IsNotExist(errOs) {
err = ErrServerRemoveUnix.Format(s.Config.ListeningAddr, errOs.Error())
return
}
s.listener, err = net.Listen("unix", s.Config.ListeningAddr)
if err != nil {
err = ErrServerPortAlreadyUsed.Return()
return
}
if err = os.Chmod(s.Config.ListeningAddr, mode); err != nil {
err = ErrServerChmod.Format(mode, s.Config.ListeningAddr, err.Error())
return
}
s.Server.Handler = s.handler
go s.Server.Serve(s.listener)
s.started = true
s.tls = false
return
}
// OpenServer opens/starts/runs/listens (to) the server, listenTLS if Cert && Key is registed, listenUnix if Mode is registed, otherwise listen
// instead of return an error this is panics on any server's error
func (s *Server) OpenServer() (err error) {
if s.Config.CertFile != "" && s.Config.KeyFile != "" {
err = s.listenTLS()
} else if s.Config.Mode > 0 {
err = s.listenUnix()
} else {
err = s.listen()
}
return
}
// CloseServer closes the server
func (s *Server) CloseServer() error {
if !s.started {
return ErrServerIsClosed.Return()
}
if s.listener != nil {
return s.listener.Close()
}
return nil
}
// parseAddr gets a slice of string and returns the address of which the Iris' server can listen
func parseAddr(fullHostOrPort ...string) string {
if len(fullHostOrPort) > 1 {
fullHostOrPort = fullHostOrPort[0:1]
}
addr := config.DefaultServerAddr // default address
// if nothing passed, then use environment's port (if any) or just :8080
if len(fullHostOrPort) == 0 {
if envPort := os.Getenv("PORT"); len(envPort) > 0 {
addr = ":" + envPort
}
} else if len(fullHostOrPort) == 1 {
addr = fullHostOrPort[0]
if strings.IndexRune(addr, ':') == -1 {
//: doesn't found on the given address, so maybe it's only a port
addr = ":" + addr
}
}
return addr
}

442
sessions/README.md Normal file
View File

@ -0,0 +1,442 @@
# Folder Information
This folder contains the sessions support for Iris. The folder name is plural (session's') so the `/sessions/providers`, because you can use both of them at the same time.
# Package information
This package is new and unique, if you notice a bug or issue [post it here](https://github.com/kataras/iris/issues).
- Cleans the temp memory when a sessions is iddle, and re-loccate it , fast, to the temp memory when it's necessary. Also most used/regular sessions are going front in the memory's list.
- Supports redisstore and normal memory routing. If redisstore is used but fails to connect then ,automatically, switching to the memory storage.
**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 site** or web application.
Instead of storing large and constantly changing information via cookies in the user's browser, **only a unique identifier is stored on the client side** (called a "session id"). This session id is passed to the web server every time the browser makes an HTTP request (ie a page link or AJAX request). The web application pairs this session id with it's internal database/memory and retrieves the stored variables for use by the requested page.
----
You will see two different ways to use the sessions, I'm using the first. No performance differences.
## How to use - easy way
Example **memory**
```go
package main
import (
"github.com/kataras/iris"
)
func main() {
// these are the defaults
//iris.Config().Session.Provider = "memory"
//iris.Config().Session.Secret = "irissessionid"
//iris.Config().Session.Life = time.Duration(60) *time.Minute
iris.Get("/set", func(c *iris.Context) {
//set session values
c.Session().Set("name", "iris")
//test if setted here
c.Write("All ok session setted to: %s", c.Session().GetString("name"))
})
iris.Get("/get", func(c *iris.Context) {
name := c.Session().GetString("name")
c.Write("The name on the /set was: %s", name)
})
iris.Get("/delete", func(c *iris.Context) {
//get the session for this context
c.Session().Delete("name")
})
iris.Get("/clear", func(c *iris.Context) {
// removes all entries
c.Session().Clear()
})
iris.Get("/destroy", func(c *iris.Context) {
//destroy, removes the entire session and cookie
c.SessionDestroy()
})
println("Server is listening at :8080")
iris.Listen("8080")
}
```
Example default **redis**
```go
package main
import (
"github.com/kataras/iris"
)
func main() {
iris.Config().Session.Provider = "redis"
iris.Get("/set", func(c *iris.Context) {
//set session values
c.Session().Set("name", "iris")
//test if setted here
c.Write("All ok session setted to: %s", c.Session().GetString("name"))
})
iris.Get("/get", func(c *iris.Context) {
name := c.Session().GetString("name")
c.Write("The name on the /set was: %s", name)
})
iris.Get("/delete", func(c *iris.Context) {
//get the session for this context
c.Session().Delete("name")
})
iris.Get("/clear", func(c *iris.Context) {
// removes all entries
c.Session().Clear()
})
iris.Get("/destroy", func(c *iris.Context) {
//destroy, removes the entire session and cookie
c.SessionDestroy()
})
println("Server is listening at :8080")
iris.Listen("8080")
}
```
Example customized **redis**
```go
// Config the redis config
type Config struct {
// Network "tcp"
Network string
// Addr "127.0.01:6379"
Addr string
// Password string .If no password then no 'AUTH'. Default ""
Password string
// If Database is empty "" then no 'SELECT'. Default ""
Database string
// MaxIdle 0 no limit
MaxIdle int
// MaxActive 0 no limit
MaxActive int
// IdleTimeout 5 * time.Minute
IdleTimeout time.Duration
// Prefix "myprefix-for-this-website". Default ""
Prefix string
// MaxAgeSeconds how much long the redis should keep the session in seconds. Default 2520.0 (42minutes)
MaxAgeSeconds int
}
```
```go
package main
import (
"github.com/kataras/iris"
"github.com/kataras/iris/sessions/providers/redis"
)
func init() {
redis.Config.Addr = "127.0.0.1:2222"
redis.Config.MaxAgeSeconds = 5000.0
}
func main() {
iris.Config().Session.Provider = "redis"
iris.Get("/set", func(c *iris.Context) {
//set session values
c.Session().Set("name", "iris")
//test if setted here
c.Write("All ok session setted to: %s", c.Session().GetString("name"))
})
iris.Get("/get", func(c *iris.Context) {
name := c.Session().GetString("name")
c.Write("The name on the /set was: %s", name)
})
iris.Get("/delete", func(c *iris.Context) {
//get the session for this context
c.Session().Delete("name")
})
iris.Get("/clear", func(c *iris.Context) {
// removes all entries
c.Session().Clear()
})
iris.Get("/destroy", func(c *iris.Context) {
//destroy, removes the entire session and cookie
c.SessionDestroy()
})
println("Server is listening at :8080")
iris.Listen("8080")
}
```
## How to use - hard way
```go
// New creates & returns a new Manager and start its GC
// accepts 4 parameters
// first is the providerName (string) ["memory","redis"]
// second is the cookieName, the session's name (string) ["mysessionsecretcookieid"]
// third is the gcDuration (time.Duration)
// when this time passes it removes from
// temporary memory GC the value which hasn't be used for a long time(gcDuration)
// this is for the client's/browser's Cookie life time(expires) also
New(provider string, cName string, gcDuration time.Duration) *sessions.Manager
```
Example **memory**
```go
package main
import (
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/sessions"
_ "github.com/kataras/iris/sessions/providers/memory" // here we add the memory provider and store
)
var sess *sessions.Manager
func init() {
sess = sessions.New("memory", "irissessionid", time.Duration(60)*time.Minute)
}
func main() {
iris.Get("/set", func(c *iris.Context) {
//get the session for this context
session := sess.Start(c)
//set session values
session.Set("name", "kataras")
//test if setted here
c.Write("All ok session setted to: %s", session.Get("name"))
})
iris.Get("/get", func(c *iris.Context) {
//get the session for this context
session := sess.Start(c)
var name string
//get the session value
if v := session.Get("name"); v != nil {
name = v.(string)
}
// OR just name = session.GetString("name")
c.Write("The name on the /set was: %s", name)
})
iris.Get("/delete", func(c *iris.Context) {
//get the session for this context
session := sess.Start(c)
session.Delete("name")
})
iris.Get("/clear", func(c *iris.Context) {
//get the session for this context
session := sess.Start(c)
// removes all entries
session.Clear()
})
iris.Get("/destroy", func(c *iris.Context) {
//destroy, removes the entire session and cookie
sess.Destroy(c)
})
iris.Listen("8080")
}
// session.GetAll() returns all values a map[interface{}]interface{}
// session.VisitAll(func(key interface{}, value interface{}) { /* loops for each entry */})
}
```
Example **redis** with default configuration
The default redis client points to 127.0.0.1:6379
```go
package main
import (
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/sessions"
_ "github.com/kataras/iris/sessions/providers/redis"
// here we add the redis provider and store
//with the default redis client points to 127.0.0.1:6379
)
var sess *sessions.Manager
func init() {
sess = sessions.New("redis", "irissessionid", time.Duration(60)*time.Minute)
}
//... usage: same as memory
```
Example **redis** with custom configuration
```go
type Config struct {
// Network "tcp"
Network string
// Addr "127.0.01:6379"
Addr string
// Password string .If no password then no 'AUTH'. Default ""
Password string
// If Database is empty "" then no 'SELECT'. Default ""
Database string
// MaxIdle 0 no limit
MaxIdle int
// MaxActive 0 no limit
MaxActive int
// IdleTimeout 5 * time.Minute
IdleTimeout time.Duration
//Prefix "myprefix-for-this-website". Default ""
Prefix string
// MaxAgeSeconds how much long the redis should keep the session in seconds. Default 2520.0 (42minutes)
MaxAgeSeconds int
}
```
```go
package main
import (
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/sessions"
"github.com/kataras/iris/sessions/providers/redis"
// here we add the redis provider and store
//with the default redis client points to 127.0.0.1:6379
)
var sess *sessions.Manager
func init() {
// you can config the redis after init also, but before any client's request
// but it's always a good idea to do it before sessions.New...
redis.Config.Network = "tcp"
redis.Config.Addr = "127.0.0.1:6379"
redis.Config.Prefix = "myprefix-for-this-website"
sess = sessions.New("redis", "irissessionid", time.Duration(60)*time.Minute)
}
//...usage: same as memory
```
### Security: Prevent session hijacking
> This section is external
**cookie only and token**
Through this simple example of hijacking a session, you can see that it's very dangerous because it allows attackers to do whatever they want. So how can we prevent session hijacking?
The first step is to only set session ids in cookies, instead of in URL rewrites. Also, we should set the httponly cookie property to true. This restricts client side scripts that want access to the session id. Using these techniques, cookies cannot be accessed by XSS and it won't be as easy as we showed to get a session id from a cookie manager.
The second step is to add a token to every request. Similar to the way we dealt with repeat forms in previous sections, we add a hidden field that contains a token. When a request is sent to the server, we can verify this token to prove that the request is unique.
```go
h := md5.New()
salt:="secret%^7&8888"
io.WriteString(h,salt+time.Now().String())
token:=fmt.Sprintf("%x",h.Sum(nil))
if r.Form["token"]!=token{
// ask to log in
}
session.Set("token",token)
```
**Session id timeout**
Another solution is to add a create time for every session, and to replace expired session ids with new ones. This can prevent session hijacking under certain circumstances.
```go
createtime := session.Get("createtime")
if createtime == nil {
session.Set("createtime", time.Now().Unix())
} else if (createtime.(int64) + 60) < (time.Now().Unix()) {
sess.Destroy(c)
session = sess.Start(c)
}
```
We set a value to save the create time and check if it's expired (I set 60 seconds here). This step can often thwart session hijacking attempts.
Combine the two solutions above and you will be able to prevent most session hijacking attempts from succeeding. On the one hand, session ids that are frequently reset will result in an attacker always getting expired and useless session ids; on the other hand, by setting the httponly property on cookies and ensuring that session ids can only be passed via cookies, all URL based attacks are mitigated.

14
sessions/errors.go Normal file
View File

@ -0,0 +1,14 @@
package sessions
import (
"github.com/kataras/iris/errors"
)
var (
// ErrProviderNotFound returns an error with message: 'Provider was not found. Please try to _ import one'
ErrProviderNotFound = errors.New("Provider with name '%s' was not found. Please try to _ import this")
// ErrProviderRegister returns an error with message: 'On provider registration. Trace: nil or empty named provider are not acceptable'
ErrProviderRegister = errors.New("On provider registration. Trace: nil or empty named provider are not acceptable")
// ErrProviderAlreadyExists returns an error with message: 'On provider registration. Trace: provider with name '%s' already exists, maybe you register it twice'
ErrProviderAlreadyExists = errors.New("On provider registration. Trace: provider with name '%s' already exists, maybe you register it twice")
)

132
sessions/manager.go Normal file
View File

@ -0,0 +1,132 @@
package sessions
import (
"encoding/base64"
"net/url"
"sync"
"time"
"github.com/kataras/iris/config"
"github.com/kataras/iris/context"
"github.com/kataras/iris/sessions/store"
"github.com/kataras/iris/utils"
"github.com/valyala/fasthttp"
)
type (
// IManager is the interface which Manager should implement
IManager interface {
Start(context.IContext) store.IStore
Destroy(context.IContext)
GC()
}
// Manager implements the IManager interface
// contains the cookie's name, the provider and a duration for GC and cookie life expire
Manager struct {
config *config.Sessions
provider IProvider
mu sync.Mutex
}
)
var _ IManager = &Manager{}
var (
continueOnError = true
providers = make(map[string]IProvider)
)
// newManager creates & returns a new Manager
func newManager(c config.Sessions) (*Manager, error) {
provider, found := providers[c.Provider]
if !found {
return nil, ErrProviderNotFound.Format(c.Provider)
}
manager := &Manager{}
manager.config = &c
manager.provider = provider
return manager, nil
}
// Register registers a provider
func Register(provider IProvider) {
if provider == nil {
ErrProviderRegister.Panic()
}
providerName := provider.Name()
if _, exists := providers[providerName]; exists {
if !continueOnError {
ErrProviderAlreadyExists.Panicf(providerName)
} else {
// do nothing it's a map it will overrides the existing provider.
}
}
providers[providerName] = provider
}
// Manager implementation
func (m *Manager) generateSessionID() string {
return base64.URLEncoding.EncodeToString(utils.Random(32))
}
// Start starts the session
func (m *Manager) Start(ctx context.IContext) store.IStore {
m.mu.Lock()
var store store.IStore
requestCtx := ctx.GetRequestCtx()
cookieValue := string(requestCtx.Request.Header.Cookie(m.config.Cookie))
if cookieValue == "" { // cookie doesn't exists, let's generate a session and add set a cookie
sid := m.generateSessionID()
store, _ = m.provider.Init(sid)
cookie := fasthttp.AcquireCookie()
cookie.SetKey(m.config.Cookie)
cookie.SetValue(url.QueryEscape(sid))
cookie.SetPath("/")
cookie.SetHTTPOnly(true)
cookie.SetExpire(m.config.Expires)
requestCtx.Response.Header.SetCookie(cookie)
fasthttp.ReleaseCookie(cookie)
//println("manager.go:156-> Setting cookie with lifetime: ", m.lifeDuration.Seconds())
} else {
sid, _ := url.QueryUnescape(cookieValue)
store, _ = m.provider.Read(sid)
}
m.mu.Unlock()
return store
}
// Destroy kills the session and remove the associated cookie
func (m *Manager) Destroy(ctx context.IContext) {
cookieValue := string(ctx.GetRequestCtx().Request.Header.Cookie(m.config.Cookie))
if cookieValue == "" { // nothing to destroy
return
}
m.mu.Lock()
m.provider.Destroy(cookieValue)
ctx.RemoveCookie(m.config.Cookie)
m.mu.Unlock()
}
// GC tick-tock for the store cleanup
// it's a blocking function, so run it with go routine, it's totally safe
func (m *Manager) GC() {
m.mu.Lock()
m.provider.GC(m.config.GcDuration)
// set a timer for the next GC
time.AfterFunc(m.config.GcDuration, func() {
m.GC()
}) // or m.expire.Unix() if Nanosecond() doesn't works here
m.mu.Unlock()
}

118
sessions/provider.go Normal file
View File

@ -0,0 +1,118 @@
package sessions
import (
"container/list"
"sync"
"time"
"github.com/kataras/iris/sessions/store"
)
// IProvider the type which Provider must implement
type IProvider interface {
Name() string
Init(string) (store.IStore, error)
Read(string) (store.IStore, error)
Destroy(string) error
Update(string) error
GC(time.Duration)
}
type (
// Provider implements the IProvider
// contains the temp sessions memory, the store and some options for the cookies
Provider struct {
name string
mu sync.Mutex
sessions map[string]*list.Element // underline TEMPORARY memory store
list *list.List // for GC
NewStore func(sessionId string, cookieLifeDuration time.Duration) store.IStore
OnDestroy func(store store.IStore) // this is called when .Destroy
cookieLifeDuration time.Duration
}
)
var _ IProvider = &Provider{}
// NewProvider returns a new empty Provider
func NewProvider(name string) *Provider {
provider := &Provider{name: name, list: list.New()}
provider.sessions = make(map[string]*list.Element, 0)
return provider
}
// Init creates the store for the first time for this session and returns it
func (p *Provider) Init(sid string) (store.IStore, error) {
p.mu.Lock()
newSessionStore := p.NewStore(sid, p.cookieLifeDuration)
elem := p.list.PushBack(newSessionStore)
p.sessions[sid] = elem
p.mu.Unlock()
return newSessionStore, nil
}
// Read returns the store which sid parameter is belongs
func (p *Provider) Read(sid string) (store.IStore, error) {
if elem, found := p.sessions[sid]; found {
return elem.Value.(store.IStore), nil
}
// if not found
sessionStore, err := p.Init(sid)
return sessionStore, err
}
// Destroy always returns a nil error, for now.
func (p *Provider) Destroy(sid string) error {
if elem, found := p.sessions[sid]; found {
elem.Value.(store.IStore).Destroy()
delete(p.sessions, sid)
p.list.Remove(elem)
}
return nil
}
// Update updates the lastAccessedTime, and moves the memory place element to the front
// always returns a nil error, for now
func (p *Provider) Update(sid string) error {
p.mu.Lock()
if elem, found := p.sessions[sid]; found {
elem.Value.(store.IStore).SetLastAccessedTime(time.Now())
p.list.MoveToFront(elem)
}
p.mu.Unlock()
return nil
}
// GC clears the memory
func (p *Provider) GC(duration time.Duration) {
p.mu.Lock()
p.cookieLifeDuration = duration
defer p.mu.Unlock() //let's defer it and trust the go
for {
elem := p.list.Back()
if elem == nil {
break
}
// if the time has passed. session was expired, then delete the session and its memory place
if (elem.Value.(store.IStore).LastAccessedTime().Unix() + duration.Nanoseconds()) < time.Now().Unix() {
p.list.Remove(elem)
delete(p.sessions, elem.Value.(store.IStore).ID())
} else {
break
}
}
}
// Name the provider's name, example: 'memory' or 'redis'
func (p *Provider) Name() string {
return p.name
}

View File

@ -0,0 +1,27 @@
package memory
import (
"time"
"github.com/kataras/iris/sessions"
"github.com/kataras/iris/sessions/store"
)
func init() {
register()
}
var (
Provider = sessions.NewProvider("memory")
)
// register registers itself (the new provider with its memory store) to the sessions providers
// must runs only once
func register() {
// the actual work is here.
Provider.NewStore = func(sessionId string, cookieLifeDuration time.Duration) store.IStore {
//println("memory.go:49-> requesting new memory store with sessionid: " + sessionId)
return &Store{sid: sessionId, lastAccessedTime: time.Now(), values: make(map[interface{}]interface{}, 0)}
}
sessions.Register(Provider)
}

View File

@ -0,0 +1,108 @@
package memory
import (
"time"
"github.com/kataras/iris/sessions/store"
)
// Store the memory store, contains the session id and the values
type Store struct {
sid string
lastAccessedTime time.Time
values map[interface{}]interface{} // here is the real memory store
}
var _ store.IStore = &Store{}
// GetAll returns all values
func (s *Store) GetAll() map[interface{}]interface{} {
return s.values
}
// VisitAll loop each one entry and calls the callback function func(key,value)
func (s *Store) VisitAll(cb func(k interface{}, v interface{})) {
for key := range s.values {
cb(key, s.values[key])
}
}
// Get returns the value of an entry by its key
func (s *Store) Get(key interface{}) interface{} {
Provider.Update(s.sid)
if value, found := s.values[key]; found {
return value
}
return nil
}
// GetString same as Get but returns as string, if nil then returns an empty string
func (s *Store) GetString(key interface{}) string {
if value := s.Get(key); value != nil {
if v, ok := value.(string); ok {
return v
}
}
return ""
}
// GetInt same as Get but returns as int, if nil then returns -1
func (s *Store) GetInt(key interface{}) int {
if value := s.Get(key); value != nil {
if v, ok := value.(int); ok {
return v
}
}
return -1
}
// Set fills the session with an entry, it receives a key and a value
// returns an error, which is always nil
func (s *Store) Set(key interface{}, value interface{}) error {
s.values[key] = value
Provider.Update(s.sid)
return nil
}
// Delete removes an entry by its key
// returns an error, which is always nil
func (s *Store) Delete(key interface{}) error {
delete(s.values, key)
Provider.Update(s.sid)
return nil
}
// Clear removes all entries
// returns an error, which is always nil
func (s *Store) Clear() error {
for key := range s.values {
delete(s.values, key)
}
Provider.Update(s.sid)
return nil
}
// ID returns the session id
func (s *Store) ID() string {
return s.sid
}
// LastAccessedTime returns the last time this session has been used
func (s *Store) LastAccessedTime() time.Time {
return s.lastAccessedTime
}
// SetLastAccessedTime updates the last accessed time
func (s *Store) SetLastAccessedTime(lastacc time.Time) {
s.lastAccessedTime = lastacc
}
// Destroy does nothing here, to destroy the session use the manager's .Destroy func
func (s *Store) Destroy() {
// nothing
}

View File

@ -0,0 +1,176 @@
package redis
import (
"time"
"github.com/kataras/iris/sessions/store"
"github.com/kataras/iris/utils"
)
/*Notes only for me
--------
Here we are setting a structure which keeps the current session's values setted by store.Set(key,value)
this is the RedisValue struct.
if noexists
RedisValue := RedisValue{sessionid,values)
RedisValue.values[thekey]=thevalue
service.Set(store.sid,RedisValue)
because we are using the same redis service for all sessions, and this is the best way to separate them,
without prefix and all that which I tried and failed to deserialize them correctly if the value is string...
so again we will keep the current server's sessions into memory
and fetch them(the sessions) from the redis at each first session run. Yes this is the fastest way to get/set a session
and at the same time they are keep saved to the redis and the GC will cleanup the memory after a while like we are doing
with the memory provider. Or just have a values field inside the Store and use just it, yes better simpler approach.
Ok then, let's convert it again.
*/
// Values is just a type of a map[interface{}]interface{}
type Values map[interface{}]interface{}
// Store the redis session store
type Store struct {
sid string
lastAccessedTime time.Time
values Values
cookieLifeDuration time.Duration //used on .Set-> SETEX on redis
}
var _ store.IStore = &Store{}
// NewStore creates and returns a new store based on the session id(string) and the cookie life duration (time.Duration)
func NewStore(sid string, cookieLifeDuration time.Duration) *Store {
s := &Store{sid: sid, lastAccessedTime: time.Now(), cookieLifeDuration: cookieLifeDuration}
//fetch the values from this session id and copy-> store them
val, err := redis.GetBytes(sid)
if err == nil {
err = utils.DeserializeBytes(val, &s.values)
if err != nil {
//if deserialization failed
s.values = Values{}
}
}
if s.values == nil {
//if key/sid wasn't found or was found but no entries in it(L72)
s.values = Values{}
}
return s
}
// serialize the values to be stored as strings inside the Redis, we panic at any serialization error here
func serialize(values Values) []byte {
val, err := utils.SerializeBytes(values)
if err != nil {
panic("On redisstore.serialize: " + err.Error())
}
return val
}
// update updates the real redis store
func (s *Store) update() {
go redis.Set(s.sid, serialize(s.values), s.cookieLifeDuration.Seconds()) //set/update all the values, in goroutine
}
// GetAll returns all values
func (s *Store) GetAll() map[interface{}]interface{} {
return s.values
}
// VisitAll loop each one entry and calls the callback function func(key,value)
func (s *Store) VisitAll(cb func(k interface{}, v interface{})) {
for key := range s.values {
cb(key, s.values[key])
}
}
// Get returns the value of an entry by its key
func (s *Store) Get(key interface{}) interface{} {
Provider.Update(s.sid)
if value, found := s.values[key]; found {
return value
}
return nil
}
// GetString same as Get but returns as string, if nil then returns an empty string
func (s *Store) GetString(key interface{}) string {
if value := s.Get(key); value != nil {
if v, ok := value.(string); ok {
return v
}
}
return ""
}
// GetInt same as Get but returns as int, if nil then returns -1
func (s *Store) GetInt(key interface{}) int {
if value := s.Get(key); value != nil {
if v, ok := value.(int); ok {
return v
}
}
return -1
}
// Set fills the session with an entry, it receives a key and a value
// returns an error, which is always nil
func (s *Store) Set(key interface{}, value interface{}) error {
s.values[key] = value
Provider.Update(s.sid)
s.update()
return nil
}
// Delete removes an entry by its key
// returns an error, which is always nil
func (s *Store) Delete(key interface{}) error {
delete(s.values, key)
Provider.Update(s.sid)
s.update()
return nil
}
// Clear removes all entries
// returns an error, which is always nil
func (s *Store) Clear() error {
//we are not using the Redis.Delete, I made so work for nothing.. we wanted only the .Set at the end...
for key := range s.values {
delete(s.values, key)
}
Provider.Update(s.sid)
s.update()
return nil
}
// ID returns the session id
func (s *Store) ID() string {
return s.sid
}
// LastAccessedTime returns the last time this session has been used
func (s *Store) LastAccessedTime() time.Time {
return s.lastAccessedTime
}
// SetLastAccessedTime updates the last accessed time
func (s *Store) SetLastAccessedTime(lastacc time.Time) {
s.lastAccessedTime = lastacc
}
// Destroy deletes entirely the session, from the memory, the client's cookie and the store
func (s *Store) Destroy() {
// remove the whole value which is the s.values from real redis
redis.Delete(s.sid)
}

View File

@ -0,0 +1,47 @@
package redis
import (
"time"
"github.com/kataras/iris/sessions"
"github.com/kataras/iris/sessions/providers/redis/service"
"github.com/kataras/iris/sessions/store"
)
func init() {
register()
}
var (
Provider = sessions.NewProvider("redis")
// redis is the default redis service, you can set configs via this object
redis = service.New()
// Config is just the Redis(service)' config
Config = redis.Config
// Empty() because maybe the user wants to edit the default configs.
//the Connect goes to the first NewStore, when user ask for session, so you have the time to change the default configs
)
// register registers itself (the new provider with its memory store) to the sessions providers
// must runs only once
func register() {
// the actual work is here.
Provider.NewStore = func(sessionId string, cookieLifeDuration time.Duration) store.IStore {
//println("memory.go:49-> requesting new memory store with sessionid: " + sessionId)
if !redis.Connected {
redis.Connect()
_, err := redis.PingPong()
if err != nil {
if err != nil {
// don't use to get the logger, just prin these to the console... atm
println("Redis Connection error on iris/sessions/providers/redisstore.Connect: " + err.Error())
println("But don't panic, auto-switching to memory store right now!")
}
}
}
return NewStore(sessionId, cookieLifeDuration)
}
sessions.Register(Provider)
}

View File

@ -0,0 +1,279 @@
package service
import (
"time"
"github.com/garyburd/redigo/redis"
"github.com/kataras/iris/config"
"github.com/kataras/iris/errors"
)
var (
// ErrRedisClosed an error with message 'Redis is already closed'
ErrRedisClosed = errors.New("Redis is already closed")
// ErrKeyNotFound an error with message 'Key $thekey doesn't found'
ErrKeyNotFound = errors.New("Key '%s' doesn't found")
)
// Service the Redis service, contains the config and the redis pool
type Service struct {
// Connected is true when the Service has already connected
Connected bool
// Config the redis config for this redis
Config *config.Redis
pool *redis.Pool
}
// PingPong sends a ping and receives a pong, if no pong received then returns false and filled error
func (r *Service) PingPong() (bool, error) {
c := r.pool.Get()
defer c.Close()
msg, err := c.Do("PING")
if err != nil || msg == nil {
return false, err
}
return (msg == "PONG"), nil
}
// CloseConnection closes the redis connection
func (r *Service) CloseConnection() error {
if r.pool != nil {
return r.pool.Close()
}
return ErrRedisClosed.Return()
}
// Set sets to the redis
// key string, value string, you can use utils.Serialize(&myobject{}) to convert an object to []byte
func (r *Service) Set(key string, value []byte, maxageseconds ...float64) (err error) { // map[interface{}]interface{}) (err error) {
maxage := config.DefaultRedisMaxAgeSeconds //1 year
c := r.pool.Get()
defer c.Close()
if err = c.Err(); err != nil {
return
}
if len(maxageseconds) > 0 {
if max := maxageseconds[0]; max >= 0 {
maxage = max
}
}
_, err = c.Do("SETEX", r.Config.Prefix+key, maxage, value)
return
}
// Get returns value, err by its key
// you can use utils.Deserialize((.Get("yourkey"),&theobject{})
//returns nil and a filled error if something wrong happens
func (r *Service) Get(key string) (interface{}, error) {
c := r.pool.Get()
defer c.Close()
if err := c.Err(); err != nil {
return nil, err
}
redisVal, err := c.Do("GET", r.Config.Prefix+key)
if err != nil {
return nil, err
}
if redisVal == nil {
return nil, ErrKeyNotFound.Format(key)
}
return redisVal, nil
}
// GetBytes returns value, err by its key
// you can use utils.Deserialize((.GetBytes("yourkey"),&theobject{})
//returns nil and a filled error if something wrong happens
func (r *Service) GetBytes(key string) ([]byte, error) {
c := r.pool.Get()
defer c.Close()
if err := c.Err(); err != nil {
return nil, err
}
redisVal, err := c.Do("GET", r.Config.Prefix+key)
if err != nil {
return nil, err
}
if redisVal == nil {
return nil, ErrKeyNotFound.Format(key)
}
return redis.Bytes(redisVal, err)
}
// GetString returns value, err by its key
// you can use utils.Deserialize((.GetString("yourkey"),&theobject{})
//returns empty string and a filled error if something wrong happens
func (r *Service) GetString(key string) (string, error) {
redisVal, err := r.Get(key)
if redisVal == nil {
return "", ErrKeyNotFound.Format(key)
}
sVal, err := redis.String(redisVal, err)
if err != nil {
return "", err
}
return sVal, nil
}
// GetInt returns value, err by its key
// you can use utils.Deserialize((.GetInt("yourkey"),&theobject{})
//returns -1 int and a filled error if something wrong happens
func (r *Service) GetInt(key string) (int, error) {
redisVal, err := r.Get(key)
if redisVal == nil {
return -1, ErrKeyNotFound.Format(key)
}
intVal, err := redis.Int(redisVal, err)
if err != nil {
return -1, err
}
return intVal, nil
}
// GetStringMap returns map[string]string, err by its key
//returns nil and a filled error if something wrong happens
func (r *Service) GetStringMap(key string) (map[string]string, error) {
redisVal, err := r.Get(key)
if redisVal == nil {
return nil, ErrKeyNotFound.Format(key)
}
_map, err := redis.StringMap(redisVal, err)
if err != nil {
return nil, err
}
return _map, nil
}
// GetAll returns all keys and their values from a specific key (map[string]string)
// returns a filled error if something bad happened
func (r *Service) GetAll(key string) (map[string]string, error) {
c := r.pool.Get()
defer c.Close()
if err := c.Err(); err != nil {
return nil, err
}
reply, err := c.Do("HGETALL", r.Config.Prefix+key)
if err != nil {
return nil, err
}
if reply == nil {
return nil, ErrKeyNotFound.Format(key)
}
return redis.StringMap(reply, err)
}
// GetAllKeysByPrefix returns all []string keys by a key prefix from the redis
func (r *Service) GetAllKeysByPrefix(prefix string) ([]string, error) {
c := r.pool.Get()
defer c.Close()
if err := c.Err(); err != nil {
return nil, err
}
reply, err := c.Do("KEYS", r.Config.Prefix+prefix)
if err != nil {
return nil, err
}
if reply == nil {
return nil, ErrKeyNotFound.Format(prefix)
}
return redis.Strings(reply, err)
}
// Delete removes redis entry by specific key
func (r *Service) Delete(key string) error {
c := r.pool.Get()
defer c.Close()
if _, err := c.Do("DEL", r.Config.Prefix+key); err != nil {
return err
}
return nil
}
func dial(network string, addr string, pass string) (redis.Conn, error) {
if network == "" {
network = config.DefaultRedisNetwork
}
if addr == "" {
addr = config.DefaultRedisAddr
}
c, err := redis.Dial(network, addr)
if err != nil {
return nil, err
}
if pass != "" {
if _, err := c.Do("AUTH", pass); err != nil {
c.Close()
return nil, err
}
}
return c, err
}
// Connect connects to the redis, called only once
func (r *Service) Connect() {
c := r.Config
if c.IdleTimeout <= 0 {
c.IdleTimeout = config.DefaultRedisIdleTimeout
}
if c.Network == "" {
c.Network = config.DefaultRedisNetwork
}
if c.Addr == "" {
c.Addr = config.DefaultRedisAddr
}
if c.MaxAgeSeconds <= 0 {
c.MaxAgeSeconds = config.DefaultRedisMaxAgeSeconds
}
pool := &redis.Pool{IdleTimeout: config.DefaultRedisIdleTimeout, MaxIdle: c.MaxIdle, MaxActive: c.MaxActive}
pool.TestOnBorrow = func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
return err
}
if c.Database != "" {
pool.Dial = func() (redis.Conn, error) {
red, err := dial(c.Network, c.Addr, c.Password)
if err != nil {
return nil, err
}
if _, err := red.Do("SELECT", c.Database); err != nil {
red.Close()
return nil, err
}
return red, err
}
} else {
pool.Dial = func() (redis.Conn, error) {
return dial(c.Network, c.Addr, c.Password)
}
}
r.Connected = true
r.pool = pool
}
// New returns a Redis service filled by the passed config
// to connect call the .Connect()
func New(cfg ...config.Redis) *Service {
c := config.DefaultRedis().Merge(cfg)
r := &Service{pool: &redis.Pool{}, Config: &c}
return r
}

14
sessions/sessions.go Normal file
View File

@ -0,0 +1,14 @@
package sessions
import "github.com/kataras/iris/config"
// New creates & returns a new Manager and start its GC
func New(cfg ...config.Sessions) *Manager {
manager, err := newManager(config.DefaultSessions().Merge(cfg))
if err != nil {
panic(err.Error()) // we have to panic here because we will start GC after and if provider is nil then many panics will come
}
//run the GC here
go manager.GC()
return manager
}

20
sessions/store/store.go Normal file
View File

@ -0,0 +1,20 @@
// Package store the package is in diffent folder to reduce the import cycles from the ./context/context.go *
package store
import "time"
// IStore is the interface which all session stores should implement
type IStore interface {
Get(interface{}) interface{}
GetString(key interface{}) string
GetInt(key interface{}) int
Set(interface{}, interface{}) error
Delete(interface{}) error
Clear() error
VisitAll(func(interface{}, interface{}))
GetAll() map[interface{}]interface{}
ID() string
LastAccessedTime() time.Time
SetLastAccessedTime(time.Time)
Destroy()
}

72
tests/httperror_test.go Normal file
View File

@ -0,0 +1,72 @@
package tests
import (
"testing"
"github.com/gavv/httpexpect"
"github.com/gavv/httpexpect/fasthttpexpect"
"github.com/kataras/iris"
"github.com/kataras/iris/config"
)
var notFoundMessage = "Iris custom message for 404 not found"
var internalServerMessage = "Iris custom message for 500 internal server error"
var routesCustomErrors = []route{
// NOT FOUND CUSTOM ERRORS - not registed
route{"GET", "/test_get_nofound_custom", "/test_get_nofound_custom", notFoundMessage, 404, false, nil, nil},
route{"POST", "/test_post_nofound_custom", "/test_post_nofound_custom", notFoundMessage, 404, false, nil, nil},
route{"PUT", "/test_put_nofound_custom", "/test_put_nofound_custom", notFoundMessage, 404, false, nil, nil},
route{"DELETE", "/test_delete_nofound_custom", "/test_delete_nofound_custom", notFoundMessage, 404, false, nil, nil},
route{"HEAD", "/test_head_nofound_custom", "/test_head_nofound_custom", notFoundMessage, 404, false, nil, nil},
route{"OPTIONS", "/test_options_nofound_custom", "/test_options_nofound_custom", notFoundMessage, 404, false, nil, nil},
route{"CONNECT", "/test_connect_nofound_custom", "/test_connect_nofound_custom", notFoundMessage, 404, false, nil, nil},
route{"PATCH", "/test_patch_nofound_custom", "/test_patch_nofound_custom", notFoundMessage, 404, false, nil, nil},
route{"TRACE", "/test_trace_nofound_custom", "/test_trace_nofound_custom", notFoundMessage, 404, false, nil, nil},
// SERVER INTERNAL ERROR 500 PANIC CUSTOM ERRORS - registed
route{"GET", "/test_get_panic_custom", "/test_get_panic_custom", internalServerMessage, 500, true, nil, nil},
route{"POST", "/test_post_panic_custom", "/test_post_panic_custom", internalServerMessage, 500, true, nil, nil},
route{"PUT", "/test_put_panic_custom", "/test_put_panic_custom", internalServerMessage, 500, true, nil, nil},
route{"DELETE", "/test_delete_panic_custom", "/test_delete_panic_custom", internalServerMessage, 500, true, nil, nil},
route{"HEAD", "/test_head_panic_custom", "/test_head_panic_custom", internalServerMessage, 500, true, nil, nil},
route{"OPTIONS", "/test_options_panic_custom", "/test_options_panic_custom", internalServerMessage, 500, true, nil, nil},
route{"CONNECT", "/test_connect_panic_custom", "/test_connect_panic_custom", internalServerMessage, 500, true, nil, nil},
route{"PATCH", "/test_patch_panic_custom", "/test_patch_panic_custom", internalServerMessage, 500, true, nil, nil},
route{"TRACE", "/test_trace_panic_custom", "/test_trace_panic_custom", internalServerMessage, 500, true, nil, nil},
}
func TestCustomErrors(t *testing.T) {
api := iris.New()
// first register the routes needed
for _, r := range routesCustomErrors {
if r.Register {
api.HandleFunc(r.Method, r.Path, func(ctx *iris.Context) {
ctx.EmitError(r.Status)
})
}
}
api.PreListen(config.Server{ListeningAddr: ""})
// create httpexpect instance that will call fasthtpp.RequestHandler directly
e := httpexpect.WithConfig(httpexpect.Config{
Reporter: httpexpect.NewAssertReporter(t),
Client: fasthttpexpect.NewBinder(api.ServeRequest),
})
// first register the custom errors
api.OnError(404, func(ctx *iris.Context) {
ctx.Write("%s", notFoundMessage)
})
api.OnError(500, func(ctx *iris.Context) {
ctx.Write("%s", internalServerMessage)
})
// run the tests
for _, r := range routesCustomErrors {
e.Request(r.Method, r.RequestPath).
Expect().
Status(r.Status).Body().Equal(r.Body)
}
}

121
tests/router_test.go Normal file
View File

@ -0,0 +1,121 @@
package tests
import (
"fmt"
"testing"
"github.com/gavv/httpexpect"
"github.com/gavv/httpexpect/fasthttpexpect"
"github.com/kataras/iris"
"github.com/kataras/iris/config"
)
type param struct {
Key string
Value string
}
type route struct {
Method string
Path string
RequestPath string
Body string
Status int
Register bool
Params []param
UrlParams []param
}
var routes = []route{
// FOUND - registed
route{"GET", "/test_get", "/test_get", "hello, get!", 200, true, nil, nil},
route{"POST", "/test_post", "/test_post", "hello, post!", 200, true, nil, nil},
route{"PUT", "/test_put", "/test_put", "hello, put!", 200, true, nil, nil},
route{"DELETE", "/test_delete", "/test_delete", "hello, delete!", 200, true, nil, nil},
route{"HEAD", "/test_head", "/test_head", "hello, head!", 200, true, nil, nil},
route{"OPTIONS", "/test_options", "/test_options", "hello, options!", 200, true, nil, nil},
route{"CONNECT", "/test_connect", "/test_connect", "hello, connect!", 200, true, nil, nil},
route{"PATCH", "/test_patch", "/test_patch", "hello, patch!", 200, true, nil, nil},
route{"TRACE", "/test_trace", "/test_trace", "hello, trace!", 200, true, nil, nil},
// NOT FOUND - not registed
route{"GET", "/test_get_nofound", "/test_get_nofound", "Not Found", 404, false, nil, nil},
route{"POST", "/test_post_nofound", "/test_post_nofound", "Not Found", 404, false, nil, nil},
route{"PUT", "/test_put_nofound", "/test_put_nofound", "Not Found", 404, false, nil, nil},
route{"DELETE", "/test_delete_nofound", "/test_delete_nofound", "Not Found", 404, false, nil, nil},
route{"HEAD", "/test_head_nofound", "/test_head_nofound", "Not Found", 404, false, nil, nil},
route{"OPTIONS", "/test_options_nofound", "/test_options_nofound", "Not Found", 404, false, nil, nil},
route{"CONNECT", "/test_connect_nofound", "/test_connect_nofound", "Not Found", 404, false, nil, nil},
route{"PATCH", "/test_patch_nofound", "/test_patch_nofound", "Not Found", 404, false, nil, nil},
route{"TRACE", "/test_trace_nofound", "/test_trace_nofound", "Not Found", 404, false, nil, nil},
// Parameters
route{"GET", "/test_get_parameter1/:name", "/test_get_parameter1/iris", "name=iris", 200, true, []param{param{"name", "iris"}}, nil},
route{"GET", "/test_get_parameter2/:name/details/:something", "/test_get_parameter2/iris/details/anything", "name=iris,something=anything", 200, true, []param{param{"name", "iris"}, param{"something", "anything"}}, nil},
route{"GET", "/test_get_parameter2/:name/details/:something/*else", "/test_get_parameter2/iris/details/anything/elsehere", "name=iris,something=anything,else=/elsehere", 200, true, []param{param{"name", "iris"}, param{"something", "anything"}, param{"else", "elsehere"}}, nil},
// URL Parameters
route{"GET", "/test_get_urlparameter1/first", "/test_get_urlparameter1/first?name=irisurl", "name=irisurl", 200, true, nil, []param{param{"name", "irisurl"}}},
route{"GET", "/test_get_urlparameter2/second", "/test_get_urlparameter2/second?name=irisurl&something=anything", "name=irisurl,something=anything", 200, true, nil, []param{param{"name", "irisurl"}, param{"something", "anything"}}},
route{"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{param{"name", "irisurl"}, param{"something", "anything"}, param{"else", "elsehere"}}},
}
func TestRouter(t *testing.T) {
api := iris.New()
for idx, _ := range routes {
r := routes[idx]
if r.Register {
api.HandleFunc(r.Method, r.Path, func(ctx *iris.Context) {
ctx.SetStatusCode(r.Status)
if r.Params != nil && len(r.Params) > 0 {
ctx.SetBodyString(ctx.Params.String())
} 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.SetBodyString(paramsKeyVal)
} else {
ctx.SetBodyString(r.Body)
}
})
}
}
api.PreListen(config.Server{ListeningAddr: ""})
// create httpexpect instance that will call fasthtpp.RequestHandler directly
e := httpexpect.WithConfig(httpexpect.Config{
Reporter: httpexpect.NewAssertReporter(t),
Client: fasthttpexpect.NewBinder(api.ServeRequest),
})
// run the tests (1)
for idx, _ := range routes {
r := routes[idx]
e.Request(r.Method, r.RequestPath).
Expect().
Status(r.Status).Body().Equal(r.Body)
}
}
func TestPathEscape(t *testing.T) {
api := iris.New()
api.Get("/details/:name", func(ctx *iris.Context) {
name := ctx.Param("name")
highlight := ctx.URLParam("highlight")
ctx.Text(iris.StatusOK, fmt.Sprintf("name=%s,highlight=%s", name, highlight))
})
api.PreListen(config.Server{ListeningAddr: ""})
api.PostListen()
e := httpexpect.WithConfig(httpexpect.Config{Reporter: httpexpect.NewAssertReporter(t), Client: fasthttpexpect.NewBinder(api.ServeRequest)})
e.Request("GET", "/details/Sakamoto desu ga?highlight=text").Expect().Status(iris.StatusOK).Body().Equal("name=Sakamoto desu ga,highlight=text")
}

18
tests/tests.go Normal file
View File

@ -0,0 +1,18 @@
// Package tests empty
/*Why empty?
The only reason I don't make unit tests is because I think the whole story here is wrong. All unit tests may succed but in practise the app fail.
believe in the real micro example-usage-tests,
but if you have different opinion make PRs here.
*/
/*Alternative:
If you want to test your API use this, new, library ,which after my suggestion has fasthttp & Iris support: https://github.com/gavv/httpexpect
I have added some test examples to this directory also in order to help you
*/
package tests
// run all verbose mode:
// go test -v
// run all:
// go test .
// run specific:
// go test -run "Router"

145
tree.go Normal file
View File

@ -0,0 +1,145 @@
package iris
import (
"bytes"
"sync"
"github.com/kataras/iris/utils"
"github.com/valyala/fasthttp"
)
type (
tree struct {
station *Iris
method string
rootBranch *Branch
domain string
hosts bool //if domain != "" we set it directly on .Plant
cors bool // if cross domain allow enabled
pool sync.Pool
next *tree
}
// Garden is the main area which routes are planted/placed
Garden struct {
first *tree
}
)
// garden
func (g *Garden) visitAll(f func(i int, tr *tree)) {
t := g.first
i := 0
for t != nil {
f(i, t)
t = t.next
}
}
// visitAllBreak like visitAll but if true to the function then it breaks
func (g *Garden) visitAllBreak(f func(i int, tr *tree) bool) {
t := g.first
i := 0
for t != nil {
if f(i, t) {
break
}
t = t.next
}
}
func (g *Garden) last() (t *tree) {
t = g.first
for t.next != nil {
t = t.next
}
return
}
// getRootByMethodAndDomain returns the correct branch which it's method&domain is equal to the given method&domain, from a garden's tree
// trees with no domain means that their domain==""
func (g *Garden) getRootByMethodAndDomain(method string, domain string) (b *Branch) {
g.visitAll(func(i int, t *tree) {
if t.domain == domain && t.method == method {
b = t.rootBranch
}
})
return
}
// Plant plants/adds a route to the garden
func (g *Garden) Plant(station *Iris, _route IRoute) {
method := _route.GetMethod()
domain := _route.GetDomain()
path := _route.GetPath()
theRoot := g.getRootByMethodAndDomain(method, domain)
if theRoot == nil {
theRoot = new(Branch)
theNewTree := newTree(station, method, theRoot, domain, len(domain) > 0, _route.HasCors())
if g.first == nil {
g.first = theNewTree
} else {
g.last().next = theNewTree
}
}
theRoot.AddBranch(domain+path, _route.GetMiddleware())
}
// tree
func newTree(station *Iris, method string, theRoot *Branch, domain string, hosts bool, hasCors bool) *tree {
t := &tree{station: station, method: method, rootBranch: theRoot, domain: domain, hosts: hosts, cors: hasCors, pool: station.newContextPool()}
return t
}
// serve serves the route
func (_tree *tree) serve(reqCtx *fasthttp.RequestCtx, path string) bool {
ctx := _tree.pool.Get().(*Context)
ctx.Reset(reqCtx)
middleware, params, mustRedirect := _tree.rootBranch.GetBranch(path, ctx.Params) // pass the parameters here for 0 allocation
if middleware != nil {
ctx.Params = params
ctx.middleware = middleware
//ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent)
ctx.Do()
_tree.pool.Put(ctx)
return true
} else if mustRedirect && !_tree.station.config.DisablePathCorrection && !bytes.Equal(reqCtx.Method(), MethodConnectBytes) {
reqPath := path
pathLen := len(reqPath)
if pathLen > 1 {
if reqPath[pathLen-1] == '/' {
reqPath = reqPath[:pathLen-1] //remove the last /
} else {
//it has path prefix, it doesn't ends with / and it hasn't be found, then just add the slash
reqPath = reqPath + "/"
}
ctx.Request.URI().SetPath(reqPath)
urlToRedirect := utils.BytesToString(ctx.Request.RequestURI())
ctx.Redirect(urlToRedirect, 301) // StatusMovedPermanently
// RFC2616 recommends that a short note "SHOULD" be included in the
// response because older user agents may not understand 301/307.
// Shouldn't send the response for POST or HEAD; that leaves GET.
if _tree.method == MethodGet {
note := "<a href=\"" + utils.HtmlEscape(urlToRedirect) + "\">Moved Permanently</a>.\n"
ctx.Write(note)
}
_tree.pool.Put(ctx)
return true
}
}
_tree.pool.Put(ctx)
return false
}

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