mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
Version 3.0.0-beta cleaned
This commit is contained in:
commit
c26668a489
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal 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
11
.travis.yml
Normal 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
32
DONATIONS.md
Normal 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¤cy_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
283
HISTORY.md
Normal 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
178
LICENSE
Normal 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
150
README.md
Normal 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
13
THIRDPARTY.md
Normal 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
14
bindings/errors.go
Normal 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
420
bindings/form.go
Normal 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
25
bindings/json.go
Normal 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
23
bindings/xml.go
Normal 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
457
branch.go
Normal 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
37
config/basicauth.go
Normal 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
16
config/config.go
Normal 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
44
config/editor.go
Normal 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
166
config/iris.go
Normal 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
40
config/iriscontrol.go
Normal 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
48
config/logger.go
Normal 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
189
config/render.go
Normal 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
44
config/server.go
Normal 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
145
config/sessions.go
Normal 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
132
config/typescript.go
Normal 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
78
config/websocket.go
Normal 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
162
context.go
Normal 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
118
context/context.go
Normal 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
196
context_renderer.go
Normal 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
129
context_request.go
Normal 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
48
context_response.go
Normal 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
157
context_storage.go
Normal 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
39
errors.go
Normal 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
6
errors/README.md
Normal 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
72
errors/error.go
Normal 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
25
graceful/README.md
Normal 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
292
graceful/graceful.go
Normal 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
119
handler.go
Normal 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
228
httperror.go
Normal 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
313
iris.go
Normal 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
52
iris/README.md
Normal 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
12
iris/doc.go
Normal 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
116
iris/main.go
Normal 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
395
iris_singleton.go
Normal 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
6
logger/README.md
Normal 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
111
logger/logger.go
Normal 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
2
middleware/README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Middleware
|
||||
Iris has its tiny middlewares here.
|
157
middleware/basicauth/basicauth.go
Normal file
157
middleware/basicauth/basicauth.go
Normal 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
97
middleware/cors/README.md
Normal 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
403
middleware/cors/cors.go
Normal 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
66
middleware/i18n/README.md
Normal 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
99
middleware/i18n/i18n.go
Normal 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
|
||||
}
|
64
middleware/logger/README.md
Normal file
64
middleware/logger/README.md
Normal 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
122
middleware/logger/logger.go
Normal 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
|
||||
}
|
30
middleware/recovery/README.md
Normal file
30
middleware/recovery/README.md
Normal 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")
|
||||
}
|
||||
|
||||
```
|
45
middleware/recovery/recovery.go
Normal file
45
middleware/recovery/recovery.go
Normal 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...)
|
||||
}
|
69
middleware/secure/README.md
Normal file
69
middleware/secure/README.md
Normal 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
205
middleware/secure/secure.go
Normal 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
123
npm/npm.go
Normal 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
674
party.go
Normal 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
394
plugin.go
Normal 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
45
plugin/editor/README.md
Normal 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
157
plugin/editor/editor.go
Normal 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)
|
||||
|
||||
}
|
48
plugin/iriscontrol/README.md
Normal file
48
plugin/iriscontrol/README.md
Normal 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")
|
||||
}
|
||||
|
||||
|
||||
|
||||
```
|
105
plugin/iriscontrol/control_panel.go
Normal file
105
plugin/iriscontrol/control_panel.go
Normal 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()
|
||||
})
|
||||
|
||||
}
|
11
plugin/iriscontrol/index.go
Normal file
11
plugin/iriscontrol/index.go
Normal 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...
|
112
plugin/iriscontrol/iriscontrol.go
Normal file
112
plugin/iriscontrol/iriscontrol.go
Normal 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)
|
||||
}
|
20
plugin/iriscontrol/main_controls.go
Normal file
20
plugin/iriscontrol/main_controls.go
Normal 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()
|
||||
}
|
||||
}
|
97
plugin/iriscontrol/user_auth.go
Normal file
97
plugin/iriscontrol/user_auth.go
Normal 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() {
|
||||
|
||||
}
|
61
plugin/routesinfo/README.md
Normal file
61
plugin/routesinfo/README.md
Normal 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")
|
||||
|
||||
}
|
||||
|
||||
|
||||
```
|
151
plugin/routesinfo/routesinfo.go
Normal file
151
plugin/routesinfo/routesinfo.go
Normal 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{}
|
||||
}
|
83
plugin/typescript/README.md
Normal file
83
plugin/typescript/README.md
Normal 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
|
102
plugin/typescript/tsconfig.go
Normal file
102
plugin/typescript/tsconfig.go
Normal 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"},
|
||||
}
|
||||
|
||||
}
|
300
plugin/typescript/typescript.go
Normal file
300
plugin/typescript/typescript.go
Normal 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
317
render/rest/engine.go
Normal 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
172
render/rest/render.go
Normal 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)
|
||||
|
||||
}
|
8
render/template/README.md
Normal file
8
render/template/README.md
Normal 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**
|
76
render/template/engine/amber/amber.go
Normal file
76
render/template/engine/amber/amber.go
Normal 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)
|
||||
}
|
227
render/template/engine/html/html.go
Normal file
227
render/template/engine/html/html.go
Normal 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)
|
||||
}
|
20
render/template/engine/jade/jade.go
Normal file
20
render/template/engine/jade/jade.go
Normal 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
|
||||
}
|
151
render/template/engine/markdown/markdown.go
Normal file
151
render/template/engine/markdown/markdown.go
Normal 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)
|
||||
}
|
189
render/template/engine/pongo/pongo.go
Normal file
189
render/template/engine/pongo/pongo.go
Normal 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
154
render/template/template.go
Normal 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
102
route.go
Normal 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
258
router.go
Normal 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
6
server/README.md
Normal 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
26
server/errors.go
Normal 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
204
server/server.go
Normal 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
442
sessions/README.md
Normal 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
14
sessions/errors.go
Normal 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
132
sessions/manager.go
Normal 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
118
sessions/provider.go
Normal 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
|
||||
}
|
27
sessions/providers/memory/register.go
Normal file
27
sessions/providers/memory/register.go
Normal 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)
|
||||
}
|
108
sessions/providers/memory/store.go
Normal file
108
sessions/providers/memory/store.go
Normal 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
|
||||
}
|
176
sessions/providers/redis/redisstore.go
Normal file
176
sessions/providers/redis/redisstore.go
Normal 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)
|
||||
}
|
47
sessions/providers/redis/register.go
Normal file
47
sessions/providers/redis/register.go
Normal 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)
|
||||
}
|
279
sessions/providers/redis/service/service.go
Normal file
279
sessions/providers/redis/service/service.go
Normal 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
14
sessions/sessions.go
Normal 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
20
sessions/store/store.go
Normal 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
72
tests/httperror_test.go
Normal 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
121
tests/router_test.go
Normal 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
18
tests/tests.go
Normal 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
145
tree.go
Normal 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
Loading…
Reference in New Issue
Block a user