mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
Happy new year! Update to 6.0.0 | HTTP/2 full support. https://github.com/kataras/iris/issues/565
full commit from development branch. Examples, book, middleware, plugins are updated to the latest iris version. Read HISTORY.md for more. The 'old' v5 branch which relied on fasthttp exists for those who want to use it navigate there: https://github.com/kataras/iris/tree/5.0.0
This commit is contained in:
parent
ced2083ab3
commit
8bbd9f8fc5
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
|
@ -1,4 +1,6 @@
|
||||||
##### Note that I do not accept pull requests and that I use the issue tracker for bug reports and proposals only. Please ask questions on the [https://kataras.rocket.chat/channel/iris][Chat] or [http://stackoverflow.com/](http://stackoverflow.com).
|
If you wanna contribute please submit a PR to one of the [iris-contrib organisation's](https://github.com/iris-contrib) projects.
|
||||||
|
|
||||||
|
##### Note that I do not accept pull requests here and that I use the issue tracker for bug reports and proposals only. Please ask questions on the [https://kataras.rocket.chat/channel/iris][Chat] or [http://stackoverflow.com/](http://stackoverflow.com).
|
||||||
|
|
||||||
## Before Submitting an Issue
|
## Before Submitting an Issue
|
||||||
|
|
||||||
|
|
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,4 +1,4 @@
|
||||||
- Version : **5.0.4**
|
- Version : **6.0.0**
|
||||||
|
|
||||||
- Operating System:
|
- Operating System:
|
||||||
|
|
||||||
|
|
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -1,5 +1 @@
|
||||||
If you are interested in contributing to the Iris project, please see the document [CONTRIBUTING](https://github.com/kataras/iris/blob/master/.github/CONTRIBUTING.md).
|
If you are interested in contributing to the Iris project, please see the document [CONTRIBUTING](https://github.com/kataras/iris/blob/master/.github/CONTRIBUTING.md).
|
||||||
|
|
||||||
##### Note that I do not accept pull requests and that I use the issue tracker for bug reports and proposals only. Please ask questions on the [https://kataras.rocket.chat/channel/iris][Chat] or [http://stackoverflow.com/](http://stackoverflow.com).
|
|
||||||
|
|
||||||
[Chat]: https://kataras.rocket.chat/channel/iris
|
|
||||||
|
|
27
DONATIONS.md
27
DONATIONS.md
|
@ -1,11 +1,6 @@
|
||||||
|
|
||||||
I spend all my time in the construction of Iris, therefore I have no income value.
|
I spend all my time in the construction of Iris, therefore I have no income value.
|
||||||
|
|
||||||
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
|
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=kataras2006%40hotmail%2ecom&lc=GR&item_name=Iris%20web%20framework&item_number=iriswebframeworkdonationid2016¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)
|
[![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=kataras2006%40hotmail%2ecom&lc=GR&item_name=Iris%20web%20framework&item_number=iriswebframeworkdonationid2016¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)
|
||||||
|
@ -27,7 +22,7 @@ I'm grateful for all the generous donations. Iris is fully funded by these dona
|
||||||
|
|
||||||
#### Donations
|
#### Donations
|
||||||
|
|
||||||
- ASKED FOR ANONYMITY* donated 50 EUR at May 11
|
- ANONYMOUS donated 50 EUR at May 11
|
||||||
|
|
||||||
- [Juan Sebastián Suárez Valencia](https://github.com/Juanses) donated 20 EUR at September 11
|
- [Juan Sebastián Suárez Valencia](https://github.com/Juanses) donated 20 EUR at September 11
|
||||||
|
|
||||||
|
@ -35,33 +30,33 @@ I'm grateful for all the generous donations. Iris is fully funded by these dona
|
||||||
|
|
||||||
- [Celso Luiz](https://github.com/celsosz) donated 50 EUR at September 29
|
- [Celso Luiz](https://github.com/celsosz) donated 50 EUR at September 29
|
||||||
|
|
||||||
- ANONYMOUS(Waiting For Approval)* donated 6 EUR at October 1
|
- ANONYMOUS donated 6 EUR at October 1
|
||||||
|
|
||||||
- [Ankur Srivastava](https://github.com/ansrivas) donated 20 EUR at October 2
|
- [Ankur Srivastava](https://github.com/ansrivas) donated 20 EUR at October 2
|
||||||
|
|
||||||
- ANONYMOUS(BY OWN WILL)* donated 100 EUR at October 18
|
- ANONYMOUS donated 100 EUR at October 18
|
||||||
|
|
||||||
- ANONYMOUS(Waiting For Approval) donated 20 EUR at October 19
|
- ANONYMOUS donated 20 EUR at October 19
|
||||||
|
|
||||||
- [Damon Zhao](https://github.com/se77en) donated 20 EUR at October 21
|
- [Damon Zhao](https://github.com/se77en) donated 20 EUR at October 21
|
||||||
|
|
||||||
- ANONYMOUS(BY OWN WILL)* donated 50 EUR at October 21
|
- ANONYMOUS donated 50 EUR at October 21
|
||||||
|
|
||||||
- [exponity - consulting & digital transformation](https://github.com/exponity) donated 30 EUR at November 4
|
- [exponity - consulting & digital transformation](https://github.com/exponity) donated 30 EUR at November 4
|
||||||
|
|
||||||
- ANONYMOUS(BY OWN WILL)* donated 50 EUR at December 7
|
- ANONYMOUS donated 50 EUR at December 7
|
||||||
|
|
||||||
- ANONYMOUS(Waiting For Approval)* donated 20 EUR at December 9
|
- ANONYMOUS donated 20 EUR at December 9
|
||||||
|
|
||||||
- ANONYMOUS(Waiting For Approval)* donated 5 EUR at December 13
|
- ANONYMOUS donated 5 EUR at December 13
|
||||||
|
|
||||||
> * The name or/and github username link added after donator's approvement via e-mail.
|
> * The name or/and github username link added after donator's approvement via e-mail.
|
||||||
|
|
||||||
#### Report, so far
|
#### Report, so far
|
||||||
|
|
||||||
- 13 EUR for the domain, [iris-go.com](https://iris-go.com)
|
- 13 EUR for the domain, [iris-go.com](https://iris-go.com)
|
||||||
|
- **Thanks** to all of **You**, 424 EUR donated to [Holy Metropolis of Ioannina](http://www.imioanninon.gr/main/) for clothes, foods and medications for our fellow human beings.
|
||||||
|
|
||||||
**Available**: VAT(50) + VAT(20) + VAT(20) + VAT(50) + VAT(6) + VAT(20) + VAT(100) + VAT(20) + VAT(20) + VAT(50) + VAT(30) + VAT(50) + VAT(20) + VAT(5) - 13 = 47,45 + 18,97 + 18,61 + 47,05 + 5,34 + 18,97 + 98,04 + 18,97 + 18,61 + 47,95 + 28,24 + 47,05 + 18,97 + 4,59 - 13 =
|
|
||||||
424,92 EUR
|
|
||||||
|
|
||||||
**All donations so far and until the end of this month(01/01/2017), will go back to the people who need them most, they are not enough but I will try to raise the amount by myself too.**
|
**Available**: VAT(50) + VAT(20) + VAT(20) + VAT(50) + VAT(6) + VAT(20) + VAT(100) + VAT(20) + VAT(20) + VAT(50) + VAT(30) + VAT(50) + VAT(20) + VAT(5) - 13 = 47,45 + 18,97 + 18,61 + 47,05 + 5,34 + 18,97 + 98,04 + 18,97 + 18,61 + 47,95 + 28,24 + 47,05 + 18,97 + 4,59 - 13 - 424 =
|
||||||
|
424,92 EUR - 424 = **0,92**
|
||||||
|
|
52
HISTORY.md
52
HISTORY.md
|
@ -2,6 +2,58 @@
|
||||||
|
|
||||||
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
|
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
|
||||||
|
|
||||||
|
|
||||||
|
## v5/fasthttp -> 6.0.0
|
||||||
|
|
||||||
|
As I [promised to the community](https://github.com/kataras/iris/issues/565) and a lot others, HTTP/2 support on Iris is happening!
|
||||||
|
|
||||||
|
I tried to minimize the side affects.
|
||||||
|
|
||||||
|
If you don't find something you used to use come here and check that conversional list:
|
||||||
|
|
||||||
|
- `context.Response.BodyWriter() io.Writer` -> `context.ResponseWriter` is a http.ResponseWriter(and io.Writer) now.
|
||||||
|
|
||||||
|
- `context.RequestCtx` removed and replaced by `context.ResponseWriter (*iris.ResponseWriter -> http.ResponseWriter)` and `context.Request (*http.Request)`
|
||||||
|
|
||||||
|
- `context.Write(string, ...string)` -> `context.Writef(string, ...string)` | Write now has this form: Write([]byte) (int,error). All other write methods didn't changed.
|
||||||
|
|
||||||
|
- `context.GetFlash/SetFlash` -> `context.Session().GetFlash/GetFlashString/SetFlash/DeleteFlash/ClearFlashes/Flashes/HasFlash`.
|
||||||
|
|
||||||
|
- `context.FormValueString(string)` -> `context.FormValue(string)`.
|
||||||
|
- `context.PathString()` -> `context.Path()`.
|
||||||
|
- `context.HostString()` -> `context.Host()`.
|
||||||
|
|
||||||
|
- `iris.Config.DisablePathEscape` was removed because now we have two methods to get a parameter `context.Param/ParamDecoded`.
|
||||||
|
|
||||||
|
|
||||||
|
- All net/http middleware/handlers are **COMPATIBLE WITH IRIS NOW**, read more there](https://github.com/iris-contrib/middleware/blob/master/README.md#can-i-use-standard-nethttp-handler-with-iris).
|
||||||
|
|
||||||
|
|
||||||
|
**Static methods changes**
|
||||||
|
|
||||||
|
- `iris.StaticServe/StaticContent/StaticEmbedded/Favicon stay as they were before this version.`.
|
||||||
|
|
||||||
|
- `iris.StaticHandler(string, int, bool, bool, []string) HandlerFunc` -> `iris.StaticHandler(reqPath string, systemPath string, showList bool, enableGzip bool) HandlerFunc`.
|
||||||
|
|
||||||
|
|
||||||
|
- `iris.StaticWeb(string, string, int) RouteNameFunc` -> `iris.StaticWeb(routePath string, systemPath string) RouteNameFunc`.
|
||||||
|
- `iris.Static` -> removed and joined to the new iris.StaticHandler
|
||||||
|
- `iris.StaticFS` -> removed and joined into the new `iris.StaticWeb`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**More on Transictions vol 4**:
|
||||||
|
|
||||||
|
- Add support for custom `transactions scopes`, two scopes already implemented: `iris.TransientTransactionScope(default) and iris.RequestTransactionScope `
|
||||||
|
|
||||||
|
- `ctx.BeginTransaction(pipe func(*iris.TransactionScope))` -> `ctx.BeginTransaction(pipe func(*iris.Transaction))`
|
||||||
|
|
||||||
|
- [from](https://github.com/iris-contrib/examples/blob/5.0.0/transactions/main.go) -> [to](https://github.com/iris-contrib/examples/blob/master/transactions/main.go). Further research `context_test.go:TestTransactions` and https://www.codeproject.com/Articles/690136/All-About-TransactionScope (for .NET C#, I got the idea from there, it's a unique(golang web) feature so please read this before use transactions inside iris)
|
||||||
|
|
||||||
|
|
||||||
|
[Examples](https://github.com/iris-contrib/examples/tree/master), [middleware](https://github.com/iris-contrib/middleware/tree/master) & [plugins](https://github.com/iris-contrib/plugin) were been refactored for this new (net/http2 compatible) release.
|
||||||
|
|
||||||
|
|
||||||
## 5.1.1 -> 5.1.3
|
## 5.1.1 -> 5.1.3
|
||||||
- **More on Transactions vol 3**: Recovery from any (unexpected error) panics inside `context.BeginTransaction` without loud, continue the execution as expected. Next version will have a little cleanup if I see that the transactions code is going very large or hard to understand the flow*
|
- **More on Transactions vol 3**: Recovery from any (unexpected error) panics inside `context.BeginTransaction` without loud, continue the execution as expected. Next version will have a little cleanup if I see that the transactions code is going very large or hard to understand the flow*
|
||||||
|
|
||||||
|
|
44
LICENSE
44
LICENSE
|
@ -1,31 +1,21 @@
|
||||||
Copyright (c) 2016 Gerasimos Maropoulos.
|
The MIT License (MIT)
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
Copyright (c) 2016-2017 Gerasimos Maropoulos
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
list of conditions and the following disclaimer.
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice, this
|
The above copyright notice and this permission notice shall be included in all
|
||||||
list of conditions and the following disclaimer in the documentation
|
copies or substantial portions of the Software.
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its contributors may
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
be used to endorse or promote products derived from this software without
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
specific prior written permission.
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
AND CONTRIBUTORS "AS IS" AND
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
|
SOFTWARE.
|
||||||
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
||||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
||||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
||||||
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
||||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
||||||
WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
||||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
247
README.md
247
README.md
|
@ -20,7 +20,7 @@
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%205.1.3%20-blue.svg?style=flat-square" alt="Releases"></a>
|
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%206.0.0%20-blue.svg?style=flat-square" alt="Releases"></a>
|
||||||
|
|
||||||
<a href="https://github.com/iris-contrib/examples"><img src="https://img.shields.io/badge/%20examples-repository-3362c2.svg?style=flat-square" alt="Examples"></a>
|
<a href="https://github.com/iris-contrib/examples"><img src="https://img.shields.io/badge/%20examples-repository-3362c2.svg?style=flat-square" alt="Examples"></a>
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
<a href="https://kataras.rocket.chat/channel/iris"><img src="https://img.shields.io/badge/%20community-chat-00BCD4.svg?style=flat-square" alt="Chat"></a><br/>
|
<a href="https://kataras.rocket.chat/channel/iris"><img src="https://img.shields.io/badge/%20community-chat-00BCD4.svg?style=flat-square" alt="Chat"></a><br/>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<b>Iris</b> is the fastest back-end web framework written in Go.
|
<b>Iris</b> is the fastest HTTP/2 web framework written in Go.
|
||||||
<br/>
|
<br/>
|
||||||
<b>Easy</b> to <a href="https://docs.iris-go.com">learn</a> while it's highly customizable,
|
<b>Easy</b> to <a href="https://docs.iris-go.com">learn</a> while it's highly customizable,
|
||||||
ideally suited for <br/> both experienced and novice developers.<br/><br/>
|
ideally suited for <br/> both experienced and novice developers.<br/><br/>
|
||||||
|
@ -39,8 +39,6 @@ Besides the fact that Iris is faster than any alternatives you may met before, <
|
||||||
If you're coming from <a href="https://nodejs.org/en/">Node.js</a> world, this is the <a href="https://github.com/expressjs/express">expressjs</a> alternative for the <a href="https://golang.org">Go Programming Language.</a>
|
If you're coming from <a href="https://nodejs.org/en/">Node.js</a> world, this is the <a href="https://github.com/expressjs/express">expressjs</a> alternative for the <a href="https://golang.org">Go Programming Language.</a>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<br/>
|
|
||||||
<img src="https://raw.githubusercontent.com/smallnest/go-web-framework-benchmark/4db507a22c964c9bc9774c5b31afdc199a0fe8b7/benchmark.png" alt="Benchmark Wizzard July 21, 2016- Processing Time Horizontal Graph" />
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,10 +47,14 @@ Feature Overview
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
- Focus on high performance
|
- Focus on high performance
|
||||||
|
- Highly customizable
|
||||||
|
- HTTP/2 full support
|
||||||
|
- Hot Reload on source code changes
|
||||||
|
- Compatible with all net/http handlers
|
||||||
- Automatically install and serve certificates from https://letsencrypt.org
|
- Automatically install and serve certificates from https://letsencrypt.org
|
||||||
- Robust routing and middleware ecosystem
|
- Robust routing and middleware ecosystem
|
||||||
- Build RESTful APIs
|
- Build RESTful APIs
|
||||||
- Request-Scoped Transactions
|
- Context Scoped Transactions
|
||||||
- Group API's and subdomains with wildcard support
|
- Group API's and subdomains with wildcard support
|
||||||
- Body binding for JSON, XML, Forms, can be extended to use your own custom binders
|
- Body binding for JSON, XML, Forms, can be extended to use your own custom binders
|
||||||
- More than 50 handy functions to send HTTP responses
|
- More than 50 handy functions to send HTTP responses
|
||||||
|
@ -61,9 +63,8 @@ Feature Overview
|
||||||
- Graceful shutdown
|
- Graceful shutdown
|
||||||
- Limit request body
|
- Limit request body
|
||||||
- Localization i18N
|
- Localization i18N
|
||||||
- Serve static files
|
- Serve static files, directories and streams
|
||||||
- Cache
|
- Fast Cache System
|
||||||
- Log requests
|
|
||||||
- Customizable format and output for the logger
|
- Customizable format and output for the logger
|
||||||
- Customizable HTTP errors
|
- Customizable HTTP errors
|
||||||
- Compression (Gzip)
|
- Compression (Gzip)
|
||||||
|
@ -71,22 +72,15 @@ Feature Overview
|
||||||
- OAuth, OAuth2 supporting 27+ popular websites
|
- OAuth, OAuth2 supporting 27+ popular websites
|
||||||
- JWT
|
- JWT
|
||||||
- Basic Authentication
|
- Basic Authentication
|
||||||
- HTTP Sessions
|
- HTTP Sessions and flash messages
|
||||||
- Add / Remove trailing slash from the URL with option to redirect
|
- Add / Remove trailing slash from the URL with option to redirect
|
||||||
- Redirect requests
|
- Redirect any request
|
||||||
- HTTP to HTTPS
|
|
||||||
- HTTP to HTTPS WWW
|
|
||||||
- HTTP to HTTPS non WWW
|
|
||||||
- Non WWW to WWW
|
|
||||||
- WWW to non WWW
|
|
||||||
- Highly scalable rich content render (Markdown, JSON, JSONP, XML...)
|
- Highly scalable rich content render (Markdown, JSON, JSONP, XML...)
|
||||||
- Websocket-only API similar to socket.io
|
- Websocket API similar to socket.io
|
||||||
- Hot Reload on source code changes
|
|
||||||
- Typescript integration + Web IDE
|
- Typescript integration + Web IDE
|
||||||
- Checks for updates at startup
|
- Optional updater
|
||||||
- Highly customizable
|
|
||||||
- Feels like you used iris forever, thanks to its Fluent API
|
- Feels like you used iris forever, thanks to its Fluent API
|
||||||
- And many others...
|
- And more...
|
||||||
|
|
||||||
Quick Start
|
Quick Start
|
||||||
-----------
|
-----------
|
||||||
|
@ -127,7 +121,7 @@ func main(){
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
iris.Listen("localhost:5700")
|
iris.Listen("localhost:5900")
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -138,48 +132,6 @@ $ go run hellojson.go
|
||||||
|
|
||||||
Open your browser or any other http client at http://localhost:5700/api/user/42.
|
Open your browser or any other http client at http://localhost:5700/api/user/42.
|
||||||
|
|
||||||
### Merry Christmas!
|
|
||||||
|
|
||||||
<p>
|
|
||||||
|
|
||||||
<img width="535" align="left" src="https://github.com/iris-contrib/website/raw/gh-pages/assets/chtree.jpg" />
|
|
||||||
|
|
||||||
This project started ~9 months ago<br/>
|
|
||||||
and rapidly won your trust,<br/>
|
|
||||||
I'm very thankful for this and<br/>
|
|
||||||
I promise you that I'll continue to<br/>
|
|
||||||
do my bests.<br/><br/>
|
|
||||||
|
|
||||||
All people, poor or rich, should give<br/>
|
|
||||||
and share with each others.
|
|
||||||
<br/><br/>
|
|
||||||
As a person who knows<br/>
|
|
||||||
how someone feels when opens<br/>
|
|
||||||
the fridge and finds nothing to eat, again, <br/>
|
|
||||||
I decided that all the <a href="https://github.com/kataras/iris/blob/master/DONATIONS.md">money you<br/>
|
|
||||||
donated so far[<i>424 EUR</i>]</a>,<br/>
|
|
||||||
and until the end of this month,<br/>
|
|
||||||
<b>should go back to the people<br/>
|
|
||||||
who need them most.</b><br/>
|
|
||||||
Is not enough but...
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
CHRISTMAS IS MOST TRULY<br/>
|
|
||||||
CHRISTMAS WHEN WE<br/>
|
|
||||||
CELEBRATE IT BY GIVING THE<br/>
|
|
||||||
LIGHT OF LOVE TO THOSE<br/>
|
|
||||||
WHO NEED IT MOST.
|
|
||||||
<br/><br/>
|
|
||||||
~ Ruth Carter Stapleton
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
### New
|
### New
|
||||||
|
|
||||||
|
@ -190,7 +142,7 @@ app := iris.New()
|
||||||
app.Listen(....)
|
app.Listen(....)
|
||||||
|
|
||||||
// New with configuration struct
|
// New with configuration struct
|
||||||
app := iris.New(iris.Configuration{ DisablePathEscape: true})
|
app := iris.New(iris.Configuration{ IsDevelopment: true})
|
||||||
|
|
||||||
app.Listen(...)
|
app.Listen(...)
|
||||||
|
|
||||||
|
@ -198,7 +150,9 @@ app.Listen(...)
|
||||||
iris.Listen(...)
|
iris.Listen(...)
|
||||||
|
|
||||||
// Default station with custom configuration
|
// Default station with custom configuration
|
||||||
iris.Config.DisablePathEscape = true
|
// view the whole configuration at: ./configuration.go
|
||||||
|
iris.Config.IsDevelopment = true
|
||||||
|
iris.Config.Charset = "UTF-8"
|
||||||
|
|
||||||
iris.Listen(...)
|
iris.Listen(...)
|
||||||
```
|
```
|
||||||
|
@ -270,8 +224,8 @@ func getProduct(ctx *iris.Context){
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func details(ctx *iris.Context){
|
func details(ctx *iris.Context){
|
||||||
color:= ctx.URLParam("color")
|
color := ctx.URLParam("color")
|
||||||
weight:= ctx.URLParamInt("weight")
|
weight,_ := ctx.URLParamInt("weight")
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -289,8 +243,8 @@ email | kataras2006@homail.com
|
||||||
```go
|
```go
|
||||||
func save(ctx *iris.Context) {
|
func save(ctx *iris.Context) {
|
||||||
// Get name and email
|
// Get name and email
|
||||||
name := ctx.FormValueString("name")
|
name := ctx.FormValue("name")
|
||||||
email := ctx.FormValueString("email")
|
email := ctx.FormValue("email")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -307,22 +261,16 @@ avatar | avatar
|
||||||
```go
|
```go
|
||||||
func save(ctx *iris.Context) {
|
func save(ctx *iris.Context) {
|
||||||
// Get name and email
|
// Get name and email
|
||||||
name := ctx.FormValueString("name")
|
name := ctx.FormValue("name")
|
||||||
email := ctx.FormValueString("email")
|
email := ctx.FormValue("email")
|
||||||
// Get avatar
|
// Get avatar
|
||||||
avatar, err := ctx.FormFile("avatar")
|
avatar, info, err := ctx.FormFile("avatar")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.EmitError(iris.StatusInternalServerError)
|
ctx.EmitError(iris.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Source
|
defer avatar.Close()
|
||||||
src, err := avatar.Open()
|
|
||||||
if err != nil {
|
|
||||||
ctx.EmitError(iris.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer src.Close()
|
|
||||||
|
|
||||||
// Destination
|
// Destination
|
||||||
dst, err := os.Create(avatar.Filename)
|
dst, err := os.Create(avatar.Filename)
|
||||||
|
@ -333,7 +281,7 @@ func save(ctx *iris.Context) {
|
||||||
defer dst.Close()
|
defer dst.Close()
|
||||||
|
|
||||||
// Copy
|
// Copy
|
||||||
if _, err = io.Copy(dst, src); err != nil {
|
if _, err = io.Copy(dst, avatar); err != nil {
|
||||||
ctx.EmitError(iris.StatusInternalServerError)
|
ctx.EmitError(iris.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -394,13 +342,13 @@ import (
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
iris.OnError(iris.StatusInternalServerError, func(ctx *iris.Context) {
|
iris.OnError(iris.StatusInternalServerError, func(ctx *iris.Context) {
|
||||||
ctx.Write("CUSTOM 500 INTERNAL SERVER ERROR PAGE")
|
ctx.Writef("CUSTOM 500 INTERNAL SERVER ERROR PAGE")
|
||||||
// or ctx.Render, ctx.HTML any render method you want
|
// or ctx.Render, ctx.HTML any render method you want
|
||||||
ctx.Log("http status: 500 happened!")
|
ctx.Log("http status: 500 happened!")
|
||||||
})
|
})
|
||||||
|
|
||||||
iris.OnError(iris.StatusNotFound, func(ctx *iris.Context) {
|
iris.OnError(iris.StatusNotFound, func(ctx *iris.Context) {
|
||||||
ctx.Write("CUSTOM 404 NOT FOUND ERROR PAGE")
|
ctx.Writef("CUSTOM 404 NOT FOUND ERROR PAGE")
|
||||||
ctx.Log("http status: 404 happened!")
|
ctx.Log("http status: 404 happened!")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -422,85 +370,44 @@ func main() {
|
||||||
|
|
||||||
### Static Content
|
### Static Content
|
||||||
|
|
||||||
Serve files or directories, use the correct for your case, if you don't know which one, just use the `Static(relative string, systemPath string, stripSlashes int)`.
|
Serve files or directories, use the correct for your case, if you don't know which one, just use the `StaticWeb(reqPath string, systemPath string)`.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// StaticHandler returns a HandlerFunc to serve static system directory
|
// Favicon serves static favicon
|
||||||
// Accepts 5 parameters
|
// accepts 2 parameters, second is optional
|
||||||
|
// favPath (string), declare the system directory path of the __.ico
|
||||||
|
// requestPath (string), it's the route's path, by default this is the "/favicon.ico" because some browsers tries to get this by default first,
|
||||||
|
// you can declare your own path if you have more than one favicon (desktop, mobile and so on)
|
||||||
//
|
//
|
||||||
// first param is the systemPath (string)
|
// 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)
|
||||||
// Path to the root directory to serve files from.
|
// Note that you have to call it on every favicon you have to serve automatically (dekstop, mobile and so on)
|
||||||
//
|
//
|
||||||
// second is the stripSlashes (int) level
|
// panics on error
|
||||||
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
|
Favicon(favPath string, requestPath ...string) RouteNameFunc
|
||||||
// * 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
|
|
||||||
//
|
|
||||||
StaticHandler(systemPath string, stripSlashes int, compress bool,
|
|
||||||
generateIndexPages bool, indexNames []string) HandlerFunc
|
|
||||||
|
|
||||||
// Static registers a route which serves a system directory
|
// StaticHandler returns a new Handler which serves static files
|
||||||
// this doesn't generates an index page which list all files
|
StaticHandler(reqPath string, systemPath string, showList bool, enableGzip bool) HandlerFunc
|
||||||
// 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: ""
|
|
||||||
Static(relative string, systemPath string, stripSlashes int)
|
|
||||||
|
|
||||||
// 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: ""
|
|
||||||
StaticFS(relative string, systemPath string, stripSlashes int)
|
|
||||||
|
|
||||||
// StaticWeb same as Static but if index.html e
|
// StaticWeb same as Static but if index.html e
|
||||||
// xists and request uri is '/' then display the index.html's contents
|
// xists and request uri is '/' then display the index.html's contents
|
||||||
// accepts three parameters
|
// accepts three parameters
|
||||||
// first parameter is the request url path (string)
|
// first parameter is the request url path (string)
|
||||||
// second parameter is the system directory (string)
|
// second parameter is the system directory (string)
|
||||||
// third parameter is the level (int) of stripSlashes
|
StaticWeb(reqPath string, systemPath string) RouteNameFunc
|
||||||
// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
|
|
||||||
// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
|
// StaticEmbedded used when files are distrubuted inside the app executable, using go-bindata mostly
|
||||||
// * stripSlashes = 2, original path: "/foo/bar", result: ""
|
// First parameter is the request path, the path which the files in the vdir will be served to, for example "/static"
|
||||||
StaticWeb(relative string, systemPath string, stripSlashes int)
|
// Second parameter is the (virtual) directory path, for example "./assets"
|
||||||
|
// Third parameter is the Asset function
|
||||||
|
// Forth parameter is the AssetNames function
|
||||||
|
//
|
||||||
|
// For more take a look at the
|
||||||
|
// example: https://github.com/iris-contrib/examples/tree/master/static_files_embedded
|
||||||
|
StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) RouteNameFunc
|
||||||
|
|
||||||
|
// StaticContent serves bytes, memory cached, on the reqPath
|
||||||
|
// a good example of this is how the websocket server uses that to auto-register the /iris-ws.js
|
||||||
|
StaticContent(reqPath string, cType string, content []byte) RouteNameFunc
|
||||||
|
|
||||||
// StaticServe serves a directory as web resource
|
// StaticServe serves a directory as web resource
|
||||||
// it's the simpliest form of the Static* functions
|
// it's the simpliest form of the Static* functions
|
||||||
|
@ -514,20 +421,16 @@ StaticServe(systemPath string, requestPath ...string)
|
||||||
```
|
```
|
||||||
|
|
||||||
```go
|
```go
|
||||||
iris.Static("/public", "./static/assets/", 1)
|
iris.StaticWeb("/public", "./static/assets/")
|
||||||
//-> /public/assets/favicon.ico
|
//-> /public/assets/favicon.ico
|
||||||
```
|
```
|
||||||
|
|
||||||
```go
|
```go
|
||||||
iris.StaticFS("/ftp", "./myfiles/public", 1)
|
iris.StaticWeb("/","./my_static_html_website")
|
||||||
```
|
```
|
||||||
|
|
||||||
```go
|
```go
|
||||||
iris.StaticWeb("/","./my_static_html_website", 1)
|
context.StaticServe(systemPath string, requestPath ...string)
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
StaticServe(systemPath string, requestPath ...string)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Manual static file serving
|
#### Manual static file serving
|
||||||
|
@ -540,7 +443,7 @@ StaticServe(systemPath string, requestPath ...string)
|
||||||
// gzipCompression (bool)
|
// gzipCompression (bool)
|
||||||
//
|
//
|
||||||
// You can define your own "Content-Type" header also, after this function call
|
// You can define your own "Content-Type" header also, after this function call
|
||||||
ServeFile(filename string, gzipCompression bool) error
|
context.ServeFile(filename string, gzipCompression bool) error
|
||||||
```
|
```
|
||||||
|
|
||||||
Serve static individual file
|
Serve static individual file
|
||||||
|
@ -673,7 +576,7 @@ The web application uses the session id as the key for retrieving the stored dat
|
||||||
|
|
||||||
```go
|
```go
|
||||||
iris.Get("/", func(ctx *iris.Context) {
|
iris.Get("/", func(ctx *iris.Context) {
|
||||||
ctx.Write("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
|
ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
|
||||||
})
|
})
|
||||||
|
|
||||||
iris.Get("/set", func(ctx *iris.Context) {
|
iris.Get("/set", func(ctx *iris.Context) {
|
||||||
|
@ -682,7 +585,7 @@ iris.Get("/", func(ctx *iris.Context) {
|
||||||
ctx.Session().Set("name", "iris")
|
ctx.Session().Set("name", "iris")
|
||||||
|
|
||||||
//test if setted here
|
//test if setted here
|
||||||
ctx.Write("All ok session setted to: %s", ctx.Session().GetString("name"))
|
ctx.Writef("All ok session setted to: %s", ctx.Session().GetString("name"))
|
||||||
})
|
})
|
||||||
|
|
||||||
iris.Get("/get", func(ctx *iris.Context) {
|
iris.Get("/get", func(ctx *iris.Context) {
|
||||||
|
@ -690,7 +593,7 @@ iris.Get("/", func(ctx *iris.Context) {
|
||||||
// returns an empty string if the key was not found.
|
// returns an empty string if the key was not found.
|
||||||
name := ctx.Session().GetString("name")
|
name := ctx.Session().GetString("name")
|
||||||
|
|
||||||
ctx.Write("The name on the /set was: %s", name)
|
ctx.Writef("The name on the /set was: %s", name)
|
||||||
})
|
})
|
||||||
|
|
||||||
iris.Get("/delete", func(ctx *iris.Context) {
|
iris.Get("/delete", func(ctx *iris.Context) {
|
||||||
|
@ -707,7 +610,7 @@ iris.Get("/", func(ctx *iris.Context) {
|
||||||
// destroy/removes the entire session and cookie
|
// destroy/removes the entire session and cookie
|
||||||
ctx.SessionDestroy()
|
ctx.SessionDestroy()
|
||||||
ctx.Log("You have to refresh the page to completely remove the session (on browsers), so the name should NOT be empty NOW, is it?\n ame: %s\n\nAlso check your cookies in your browser's cookies, should be no field for localhost/127.0.0.1 (or whatever you use)", ctx.Session().GetString("name"))
|
ctx.Log("You have to refresh the page to completely remove the session (on browsers), so the name should NOT be empty NOW, is it?\n ame: %s\n\nAlso check your cookies in your browser's cookies, should be no field for localhost/127.0.0.1 (or whatever you use)", ctx.Session().GetString("name"))
|
||||||
ctx.Write("You have to refresh the page to completely remove the session (on browsers), so the name should NOT be empty NOW, is it?\nName: %s\n\nAlso check your cookies in your browser's cookies, should be no field for localhost/127.0.0.1 (or whatever you use)", ctx.Session().GetString("name"))
|
ctx.Writef("You have to refresh the page to completely remove the session (on browsers), so the name should NOT be empty NOW, is it?\nName: %s\n\nAlso check your cookies in your browser's cookies, should be no field for localhost/127.0.0.1 (or whatever you use)", ctx.Session().GetString("name"))
|
||||||
})
|
})
|
||||||
|
|
||||||
iris.Listen(":8080")
|
iris.Listen(":8080")
|
||||||
|
@ -738,7 +641,7 @@ func main() {
|
||||||
iris.Static("/js", "./static/js", 1)
|
iris.Static("/js", "./static/js", 1)
|
||||||
|
|
||||||
iris.Get("/", func(ctx *iris.Context) {
|
iris.Get("/", func(ctx *iris.Context) {
|
||||||
ctx.Render("client.html", clientPage{"Client Page", ctx.HostString()})
|
ctx.Render("client.html", clientPage{"Client Page", ctx.Host()})
|
||||||
})
|
})
|
||||||
|
|
||||||
// the path at which the websocket client should register itself to
|
// the path at which the websocket client should register itself to
|
||||||
|
@ -915,21 +818,14 @@ You can read the full article [here](https://translate.google.com/translate?sl=a
|
||||||
Testing
|
Testing
|
||||||
------------
|
------------
|
||||||
|
|
||||||
I recommend writing your API tests using this new library, [httpexpect](https://github.com/gavv/httpexpect) which supports Iris and fasthttp now, after my request [here](https://github.com/gavv/httpexpect/issues/2). You can find Iris examples [here](https://github.com/gavv/httpexpect/blob/master/_examples/iris_test.go), [here](https://github.com/kataras/iris/blob/master/http_test.go) and [here](https://github.com/kataras/iris/blob/master/context_test.go).
|
I recommend writing your API tests using this new library, [httpexpect](https://github.com/gavv/httpexpect). You can find Iris examples [here](https://github.com/gavv/httpexpect/blob/master/_examples/iris_test.go), [here](https://github.com/kataras/iris/blob/master/http_test.go) and [here](https://github.com/kataras/iris/blob/master/context_test.go).
|
||||||
|
|
||||||
Versioning
|
Versioning
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Current: **v5.1.3**
|
Current: **v6.0.0**
|
||||||
|
|
||||||
Stable: **[v4 LTS](https://github.com/kataras/iris/tree/4.0.0#versioning)**
|
Stable: **[v5/fasthttp](https://github.com/kataras/iris/tree/5.0.0)**
|
||||||
|
|
||||||
|
|
||||||
Todo
|
|
||||||
------------
|
|
||||||
|
|
||||||
- [ ] Server-side React render, as requested [here](https://github.com/kataras/iris/issues/503)
|
|
||||||
- [x] [v5.1.0: (Request) Scoped Transactions](https://github.com/iris-contrib/examples/tree/master/transactions), simple and elegant.
|
|
||||||
|
|
||||||
|
|
||||||
Iris is a **Community-Driven** Project, waiting for your suggestions and [feature requests](https://github.com/kataras/iris/issues?utf8=%E2%9C%93&q=label%3A%22feature%20request%22)!
|
Iris is a **Community-Driven** Project, waiting for your suggestions and [feature requests](https://github.com/kataras/iris/issues?utf8=%E2%9C%93&q=label%3A%22feature%20request%22)!
|
||||||
|
@ -949,11 +845,10 @@ Iris is the work of hundreds of the community's [feature requests](https://githu
|
||||||
|
|
||||||
If you are interested in contributing to the Iris project, please see the document [CONTRIBUTING](https://github.com/kataras/iris/blob/master/.github/CONTRIBUTING.md).
|
If you are interested in contributing to the Iris project, please see the document [CONTRIBUTING](https://github.com/kataras/iris/blob/master/.github/CONTRIBUTING.md).
|
||||||
|
|
||||||
##### Note that I do not accept pull requests and that I use the issue tracker for bug reports and proposals only. Please ask questions on the [https://kataras.rocket.chat/channel/iris][Chat] or [http://stackoverflow.com/](http://stackoverflow.com).
|
|
||||||
|
|
||||||
Depends on:
|
Depends on:
|
||||||
|
|
||||||
- http protocol layer comes from [valyala/fasthttp](https://github.com/valyala/fasthttp), by Aliaksandr Valialkin.
|
- http protocol layer comes from [net/http](https://github.com/golang/go/tree/master/src/net/http), by Go Authors.
|
||||||
- rich and encoded responses support comes from [kataras/go-serializer](https://github.com/kataras/go-serializer/tree/0.0.4), by me.
|
- rich and encoded responses support comes from [kataras/go-serializer](https://github.com/kataras/go-serializer/tree/0.0.4), by me.
|
||||||
- template support comes from [kataras/go-template](https://github.com/kataras/go-template/tree/0.0.3), by me.
|
- template support comes from [kataras/go-template](https://github.com/kataras/go-template/tree/0.0.3), by me.
|
||||||
- gzip support comes from [kataras/go-fs](https://github.com/kataras/go-fs/tree/0.0.5) and the super-fast compression library [klauspost/compress/gzip](https://github.com/klauspost/compress/tree/master/gzip), by me & Klaus Post.
|
- gzip support comes from [kataras/go-fs](https://github.com/kataras/go-fs/tree/0.0.5) and the super-fast compression library [klauspost/compress/gzip](https://github.com/klauspost/compress/tree/master/gzip), by me & Klaus Post.
|
||||||
|
@ -983,7 +878,7 @@ License
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Unless otherwise noted, the `iris` source files are distributed
|
Unless otherwise noted, the `iris` source files are distributed
|
||||||
under the BSD-3 Clause license found in the [LICENSE file](LICENSE).
|
under the MIT License found in the [LICENSE file](LICENSE).
|
||||||
|
|
||||||
|
|
||||||
[Chat]: https://kataras.rocket.chat/channel/iris
|
[Chat]: https://kataras.rocket.chat/channel/iris
|
||||||
|
|
228
configuration.go
228
configuration.go
|
@ -1,7 +1,10 @@
|
||||||
package iris
|
package iris
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -10,7 +13,6 @@ import (
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
"github.com/kataras/go-options"
|
"github.com/kataras/go-options"
|
||||||
"github.com/kataras/go-sessions"
|
"github.com/kataras/go-sessions"
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -52,7 +54,7 @@ type Configuration struct {
|
||||||
// when calling the template helper '{{url }}'
|
// when calling the template helper '{{url }}'
|
||||||
// *keep note that you can use {{urlpath }}) instead*
|
// *keep note that you can use {{urlpath }}) instead*
|
||||||
//
|
//
|
||||||
// Note: this is the main's server Host, you can setup unlimited number of fasthttp servers
|
// Note: this is the main's server Host, you can setup unlimited number of net/http servers
|
||||||
// listening to the $instance.Handler after the manually-called $instance.Build
|
// listening to the $instance.Handler after the manually-called $instance.Build
|
||||||
//
|
//
|
||||||
// Default comes from iris.Listen/.Serve with iris' listeners (iris.TCP4/UNIX/TLS/LETSENCRYPT)
|
// Default comes from iris.Listen/.Serve with iris' listeners (iris.TCP4/UNIX/TLS/LETSENCRYPT)
|
||||||
|
@ -67,52 +69,30 @@ type Configuration struct {
|
||||||
// Default comes from iris.Listen/.Serve with iris' listeners (TCP4,UNIX,TLS,LETSENCRYPT)
|
// Default comes from iris.Listen/.Serve with iris' listeners (TCP4,UNIX,TLS,LETSENCRYPT)
|
||||||
VScheme string
|
VScheme string
|
||||||
|
|
||||||
// MaxRequestBodySize Maximum request body size.
|
ReadTimeout time.Duration // maximum duration before timing out read of the request
|
||||||
//
|
WriteTimeout time.Duration // maximum duration before timing out write of the response
|
||||||
// The server rejects requests with bodies exceeding this limit.
|
|
||||||
//
|
|
||||||
// By default request body size is 8MB.
|
|
||||||
MaxRequestBodySize int
|
|
||||||
|
|
||||||
// Per-connection buffer size for requests' reading.
|
// MaxHeaderBytes controls the maximum number of bytes the
|
||||||
// This also limits the maximum header size.
|
// server will read parsing the request header's keys and
|
||||||
//
|
// values, including the request line. It does not limit the
|
||||||
// Increase this buffer if your clients send multi-KB RequestURIs
|
// size of the request body.
|
||||||
// and/or multi-KB headers (for example, BIG cookies).
|
// If zero, DefaultMaxHeaderBytes is used.
|
||||||
//
|
MaxHeaderBytes int
|
||||||
// Default buffer size is used if not set.
|
|
||||||
ReadBufferSize int
|
|
||||||
|
|
||||||
// Per-connection buffer size for responses' writing.
|
// TLSNextProto optionally specifies a function to take over
|
||||||
//
|
// ownership of the provided TLS connection when an NPN/ALPN
|
||||||
// Default buffer size is used if not set.
|
// protocol upgrade has occurred. The map key is the protocol
|
||||||
WriteBufferSize int
|
// name negotiated. The Handler argument should be used to
|
||||||
|
// handle HTTP requests and will initialize the Request's TLS
|
||||||
|
// and RemoteAddr if not already set. The connection is
|
||||||
|
// automatically closed when the function returns.
|
||||||
|
// If TLSNextProto is nil, HTTP/2 support is enabled automatically.
|
||||||
|
TLSNextProto map[string]func(*http.Server, *tls.Conn, http.Handler)
|
||||||
|
|
||||||
// Maximum duration for reading the full request (including body).
|
// ConnState specifies an optional callback function that is
|
||||||
//
|
// called when a client connection changes state. See the
|
||||||
// This also limits the maximum duration for idle keep-alive
|
// ConnState type and associated constants for details.
|
||||||
// connections.
|
ConnState func(net.Conn, http.ConnState)
|
||||||
//
|
|
||||||
// By default request read timeout is unlimited.
|
|
||||||
ReadTimeout time.Duration
|
|
||||||
|
|
||||||
// Maximum duration for writing the full response (including body).
|
|
||||||
//
|
|
||||||
// By default response write timeout is unlimited.
|
|
||||||
WriteTimeout time.Duration
|
|
||||||
|
|
||||||
// Maximum number of concurrent client connections allowed per IP.
|
|
||||||
//
|
|
||||||
// By default unlimited number of concurrent connections
|
|
||||||
MaxConnsPerIP int
|
|
||||||
|
|
||||||
// Maximum number of requests served per connection.
|
|
||||||
//
|
|
||||||
// The server closes connection after the last request.
|
|
||||||
// 'Connection: close' header is added to the last response.
|
|
||||||
//
|
|
||||||
// By default unlimited number of requests may be served per connection.
|
|
||||||
MaxRequestsPerConn int
|
|
||||||
|
|
||||||
// CheckForUpdates will try to search for newer version of Iris based on the https://github.com/kataras/iris/releases
|
// CheckForUpdates will try to search for newer version of Iris based on the https://github.com/kataras/iris/releases
|
||||||
// If a newer version found then the app will ask the he dev/user if want to update the 'x' version
|
// If a newer version found then the app will ask the he dev/user if want to update the 'x' version
|
||||||
|
@ -231,7 +211,7 @@ var (
|
||||||
// when calling the template helper '{{url }}'
|
// when calling the template helper '{{url }}'
|
||||||
// *keep note that you can use {{urlpath }}) instead*
|
// *keep note that you can use {{urlpath }}) instead*
|
||||||
//
|
//
|
||||||
// Note: this is the main's server Host, you can setup unlimited number of fasthttp servers
|
// Note: this is the main's server Host, you can setup unlimited number of net/http servers
|
||||||
// listening to the $instance.Handler after the manually-called $instance.Build
|
// listening to the $instance.Handler after the manually-called $instance.Build
|
||||||
//
|
//
|
||||||
// Default comes from iris.Listen/.Serve with iris' listeners (iris.TCP4/UNIX/TLS/LETSENCRYPT)
|
// Default comes from iris.Listen/.Serve with iris' listeners (iris.TCP4/UNIX/TLS/LETSENCRYPT)
|
||||||
|
@ -253,80 +233,50 @@ var (
|
||||||
c.VScheme = val
|
c.VScheme = val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// maximum duration before timing out read of the request
|
||||||
// OptionMaxRequestBodySize Maximum request body size.
|
|
||||||
//
|
|
||||||
// The server rejects requests with bodies exceeding this limit.
|
|
||||||
//
|
|
||||||
// By default request body size is 8MB.
|
|
||||||
OptionMaxRequestBodySize = func(val int) OptionSet {
|
|
||||||
return func(c *Configuration) {
|
|
||||||
c.MaxRequestBodySize = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Per-connection buffer size for requests' reading.``
|
|
||||||
// This also limits the maximum header size.
|
|
||||||
//
|
|
||||||
// Increase this buffer if your clients send multi-KB RequestURIs
|
|
||||||
// and/or multi-KB headers (for example, BIG cookies).
|
|
||||||
//
|
|
||||||
// Default buffer size is used if not set.
|
|
||||||
OptionReadBufferSize = func(val int) OptionSet {
|
|
||||||
return func(c *Configuration) {
|
|
||||||
c.ReadBufferSize = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Per-connection buffer size for responses' writing.
|
|
||||||
//
|
|
||||||
// Default buffer size is used if not set.
|
|
||||||
OptionWriteBufferSize = func(val int) OptionSet {
|
|
||||||
return func(c *Configuration) {
|
|
||||||
c.WriteBufferSize = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maximum duration for reading the full request (including body).
|
|
||||||
//
|
|
||||||
// This also limits the maximum duration for idle keep-alive
|
|
||||||
// connections.
|
|
||||||
//
|
|
||||||
// By default request read timeout is unlimited.
|
|
||||||
OptionReadTimeout = func(val time.Duration) OptionSet {
|
OptionReadTimeout = func(val time.Duration) OptionSet {
|
||||||
return func(c *Configuration) {
|
return func(c *Configuration) {
|
||||||
c.ReadTimeout = val
|
c.ReadTimeout = val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// maximum duration before timing out write of the response
|
||||||
// Maximum duration for writing the full response (including body).
|
|
||||||
//
|
|
||||||
// By default response write timeout is unlimited.
|
|
||||||
OptionWriteTimeout = func(val time.Duration) OptionSet {
|
OptionWriteTimeout = func(val time.Duration) OptionSet {
|
||||||
return func(c *Configuration) {
|
return func(c *Configuration) {
|
||||||
c.WriteTimeout = val
|
c.WriteTimeout = val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptionMaxConnsPerIP Maximum number of concurrent client connections allowed per IP.
|
// MaxHeaderBytes controls the maximum number of bytes the
|
||||||
//
|
// server will read parsing the request header's keys and
|
||||||
// By default unlimited number of concurrent connections
|
// values, including the request line. It does not limit the
|
||||||
// may be established to the server from a single IP address.
|
// size of the request body.
|
||||||
OptionMaxConnsPerIP = func(val int) OptionSet {
|
// If zero, DefaultMaxHeaderBytes(8MB) is used.
|
||||||
|
OptionMaxHeaderBytes = func(val int) OptionSet {
|
||||||
return func(c *Configuration) {
|
return func(c *Configuration) {
|
||||||
c.MaxConnsPerIP = val
|
c.MaxHeaderBytes = val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptionMaxRequestsPerConn Maximum number of requests served per connection.
|
// TLSNextProto optionally specifies a function to take over
|
||||||
//
|
// ownership of the provided TLS connection when an NPN/ALPN
|
||||||
// The server closes connection after the last request.
|
// protocol upgrade has occurred. The map key is the protocol
|
||||||
// 'Connection: close' header is added to the last response.
|
// name negotiated. The Handler argument should be used to
|
||||||
//
|
// handle HTTP requests and will initialize the Request's TLS
|
||||||
// By default unlimited number of requests may be served per connection.
|
// and RemoteAddr if not already set. The connection is
|
||||||
OptionMaxRequestsPerConn = func(val int) OptionSet {
|
// automatically closed when the function returns.
|
||||||
|
// If TLSNextProto is nil, HTTP/2 support is enabled automatically.
|
||||||
|
OptionTLSNextProto = func(val map[string]func(*http.Server, *tls.Conn, http.Handler)) OptionSet {
|
||||||
return func(c *Configuration) {
|
return func(c *Configuration) {
|
||||||
c.MaxRequestsPerConn = val
|
c.TLSNextProto = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnState specifies an optional callback function that is
|
||||||
|
// called when a client connection changes state. See the
|
||||||
|
// ConnState type and associated constants for details.
|
||||||
|
OptionConnState = func(val func(net.Conn, http.ConnState)) OptionSet {
|
||||||
|
return func(c *Configuration) {
|
||||||
|
c.ConnState = val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -480,11 +430,6 @@ var (
|
||||||
DefaultTimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
|
DefaultTimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
|
||||||
// StaticCacheDuration expiration duration for INACTIVE file handlers, it's a global configuration field to all iris instances
|
// StaticCacheDuration expiration duration for INACTIVE file handlers, it's a global configuration field to all iris instances
|
||||||
StaticCacheDuration = 20 * time.Second
|
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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Default values for base Iris conf
|
// Default values for base Iris conf
|
||||||
|
@ -493,9 +438,6 @@ const (
|
||||||
DefaultDisablePathEscape = false
|
DefaultDisablePathEscape = false
|
||||||
DefaultCharset = "UTF-8"
|
DefaultCharset = "UTF-8"
|
||||||
DefaultLoggerPreffix = "[IRIS] "
|
DefaultLoggerPreffix = "[IRIS] "
|
||||||
// DefaultMaxRequestBodySize is 8MB
|
|
||||||
DefaultMaxRequestBodySize = 2 * fasthttp.DefaultMaxRequestBodySize
|
|
||||||
|
|
||||||
// Per-connection buffer size for requests' reading.
|
// Per-connection buffer size for requests' reading.
|
||||||
// This also limits the maximum header size.
|
// This also limits the maximum header size.
|
||||||
//
|
//
|
||||||
|
@ -503,19 +445,17 @@ const (
|
||||||
// and/or multi-KB headers (for example, BIG cookies).
|
// and/or multi-KB headers (for example, BIG cookies).
|
||||||
//
|
//
|
||||||
// Default buffer size is 8MB
|
// Default buffer size is 8MB
|
||||||
DefaultReadBufferSize = 8096
|
DefaultMaxHeaderBytes = 8096
|
||||||
|
|
||||||
// Per-connection buffer size for responses' writing.
|
// DefaultReadTimeout no read client timeout
|
||||||
//
|
DefaultReadTimeout = 0
|
||||||
// Default buffer size is 8MB
|
// DefaultWriteTimeout no serve client timeout
|
||||||
DefaultWriteBufferSize = 8096
|
DefaultWriteTimeout = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// DefaultLoggerOut is the default logger's output
|
// DefaultLoggerOut is the default logger's output
|
||||||
DefaultLoggerOut = os.Stdout
|
DefaultLoggerOut = os.Stdout
|
||||||
// DefaultServerName the response header of the 'Server' value when writes to the client
|
|
||||||
DefaultServerName = ""
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultConfiguration returns the default configuration for an Iris station, fills the main Configuration
|
// DefaultConfiguration returns the default configuration for an Iris station, fills the main Configuration
|
||||||
|
@ -523,11 +463,9 @@ func DefaultConfiguration() Configuration {
|
||||||
return Configuration{
|
return Configuration{
|
||||||
VHost: "",
|
VHost: "",
|
||||||
VScheme: "",
|
VScheme: "",
|
||||||
MaxRequestBodySize: DefaultMaxRequestBodySize,
|
ReadTimeout: DefaultReadTimeout,
|
||||||
ReadBufferSize: DefaultReadBufferSize,
|
WriteTimeout: DefaultWriteTimeout,
|
||||||
WriteBufferSize: DefaultWriteBufferSize,
|
MaxHeaderBytes: DefaultMaxHeaderBytes,
|
||||||
MaxConnsPerIP: 0,
|
|
||||||
MaxRequestsPerConn: 0,
|
|
||||||
CheckForUpdates: false,
|
CheckForUpdates: false,
|
||||||
CheckForUpdatesSync: false,
|
CheckForUpdatesSync: false,
|
||||||
DisablePathCorrection: DefaultDisablePathCorrection,
|
DisablePathCorrection: DefaultDisablePathCorrection,
|
||||||
|
@ -675,14 +613,14 @@ type WebsocketConfiguration struct {
|
||||||
// Error specifies the function for generating HTTP error responses.
|
// Error specifies the function for generating HTTP error responses.
|
||||||
//
|
//
|
||||||
// The default behavior is to store the reason in the context (ctx.Set(reason)) and fire any custom error (ctx.EmitError(status))
|
// The default behavior is to store the reason in the context (ctx.Set(reason)) and fire any custom error (ctx.EmitError(status))
|
||||||
Error func(ctx *Context, status int, reason string)
|
Error func(ctx *Context, status int, reason error)
|
||||||
// CheckOrigin returns true if the request Origin header is acceptable. If
|
// CheckOrigin returns true if the request Origin header is acceptable. If
|
||||||
// CheckOrigin is nil, the host in the Origin header must not be set or
|
// CheckOrigin is nil, the host in the Origin header must not be set or
|
||||||
// must match the host of the request.
|
// must match the host of the request.
|
||||||
//
|
//
|
||||||
// The default behavior is to allow all origins
|
// The default behavior is to allow all origins
|
||||||
// you can change this behavior by setting the iris.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin
|
// you can change this behavior by setting the iris.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin
|
||||||
CheckOrigin func(ctx *Context) bool
|
CheckOrigin func(r *http.Request) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -748,7 +686,7 @@ var (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// OptionWebsocketError specifies the function for generating HTTP error responses.
|
// OptionWebsocketError specifies the function for generating HTTP error responses.
|
||||||
OptionWebsocketError = func(val func(*Context, int, string)) OptionSet {
|
OptionWebsocketError = func(val func(*Context, int, error)) OptionSet {
|
||||||
return func(c *Configuration) {
|
return func(c *Configuration) {
|
||||||
c.Websocket.Error = val
|
c.Websocket.Error = val
|
||||||
}
|
}
|
||||||
|
@ -756,7 +694,7 @@ var (
|
||||||
// OptionWebsocketCheckOrigin returns true if the request Origin header is acceptable. If
|
// OptionWebsocketCheckOrigin returns true if the request Origin header is acceptable. If
|
||||||
// CheckOrigin is nil, the host in the Origin header must not be set or
|
// CheckOrigin is nil, the host in the Origin header must not be set or
|
||||||
// must match the host of the request.
|
// must match the host of the request.
|
||||||
OptionWebsocketCheckOrigin = func(val func(*Context) bool) OptionSet {
|
OptionWebsocketCheckOrigin = func(val func(*http.Request) bool) OptionSet {
|
||||||
return func(c *Configuration) {
|
return func(c *Configuration) {
|
||||||
c.Websocket.CheckOrigin = val
|
c.Websocket.CheckOrigin = val
|
||||||
}
|
}
|
||||||
|
@ -764,30 +702,30 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DefaultWriteTimeout 15 * time.Second
|
// DefaultWebsocketWriteTimeout 15 * time.Second
|
||||||
DefaultWriteTimeout = 15 * time.Second
|
DefaultWebsocketWriteTimeout = 15 * time.Second
|
||||||
// DefaultPongTimeout 60 * time.Second
|
// DefaultWebsocketPongTimeout 60 * time.Second
|
||||||
DefaultPongTimeout = 60 * time.Second
|
DefaultWebsocketPongTimeout = 60 * time.Second
|
||||||
// DefaultPingPeriod (DefaultPongTimeout * 9) / 10
|
// DefaultWebsocketPingPeriod (DefaultPongTimeout * 9) / 10
|
||||||
DefaultPingPeriod = (DefaultPongTimeout * 9) / 10
|
DefaultWebsocketPingPeriod = (DefaultWebsocketPongTimeout * 9) / 10
|
||||||
// DefaultMaxMessageSize 1024
|
// DefaultWebsocketMaxMessageSize 1024
|
||||||
DefaultMaxMessageSize = 1024
|
DefaultWebsocketMaxMessageSize = 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// DefaultWebsocketError is the default method to manage the handshake websocket errors
|
// DefaultWebsocketError is the default method to manage the handshake websocket errors
|
||||||
DefaultWebsocketError = func(ctx *Context, status int, reason string) {
|
DefaultWebsocketError = func(ctx *Context, status int, reason error) {
|
||||||
ctx.Set("WsError", reason)
|
ctx.Set("WsError", reason)
|
||||||
ctx.EmitError(status)
|
ctx.EmitError(status)
|
||||||
}
|
}
|
||||||
// DefaultWebsocketCheckOrigin is the default method to allow websocket clients to connect to this server
|
// DefaultWebsocketCheckOrigin is the default method to allow websocket clients to connect to this server
|
||||||
// you can change this behavior by setting the iris.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin
|
// you can change this behavior by setting the iris.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin
|
||||||
DefaultWebsocketCheckOrigin = func(ctx *Context) bool {
|
DefaultWebsocketCheckOrigin = func(r *http.Request) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// WebsocketCheckSameOrigin returns true if the origin is not set or is equal to the request host
|
// WebsocketCheckSameOrigin returns true if the origin is not set or is equal to the request host
|
||||||
WebsocketCheckSameOrigin = func(ctx *Context) bool {
|
WebsocketCheckSameOrigin = func(r *http.Request) bool {
|
||||||
origin := ctx.RequestHeader("origin")
|
origin := r.Header.Get("origin")
|
||||||
if len(origin) == 0 {
|
if len(origin) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -795,17 +733,17 @@ var (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return u.Host == ctx.HostString()
|
return u.Host == r.Host
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultWebsocketConfiguration returns the default config for iris-ws websocket package
|
// DefaultWebsocketConfiguration returns the default config for iris-ws websocket package
|
||||||
func DefaultWebsocketConfiguration() WebsocketConfiguration {
|
func DefaultWebsocketConfiguration() WebsocketConfiguration {
|
||||||
return WebsocketConfiguration{
|
return WebsocketConfiguration{
|
||||||
WriteTimeout: DefaultWriteTimeout,
|
WriteTimeout: DefaultWebsocketWriteTimeout,
|
||||||
PongTimeout: DefaultPongTimeout,
|
PongTimeout: DefaultWebsocketPongTimeout,
|
||||||
PingPeriod: DefaultPingPeriod,
|
PingPeriod: DefaultWebsocketPingPeriod,
|
||||||
MaxMessageSize: DefaultMaxMessageSize,
|
MaxMessageSize: DefaultWebsocketMaxMessageSize,
|
||||||
BinaryMessages: false,
|
BinaryMessages: false,
|
||||||
ReadBufferSize: 4096,
|
ReadBufferSize: 4096,
|
||||||
WriteBufferSize: 4096,
|
WriteBufferSize: 4096,
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
package iris_test
|
package iris_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kataras/iris"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kataras/iris"
|
||||||
)
|
)
|
||||||
|
|
||||||
// go test -v -run TestConfig*
|
// go test -v -run TestConfig*
|
||||||
|
|
1138
context.go
1138
context.go
File diff suppressed because it is too large
Load Diff
410
context_test.go
410
context_test.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -14,7 +15,6 @@ import (
|
||||||
"github.com/gavv/httpexpect"
|
"github.com/gavv/httpexpect"
|
||||||
"github.com/kataras/iris"
|
"github.com/kataras/iris"
|
||||||
"github.com/kataras/iris/httptest"
|
"github.com/kataras/iris/httptest"
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// White-box testing *
|
// White-box testing *
|
||||||
|
@ -72,7 +72,7 @@ type pathParameters []pathParameter
|
||||||
|
|
||||||
// White-box testing *
|
// White-box testing *
|
||||||
func TestContextParams(t *testing.T) {
|
func TestContextParams(t *testing.T) {
|
||||||
context := &iris.Context{RequestCtx: &fasthttp.RequestCtx{}}
|
context := &iris.Context{}
|
||||||
params := pathParameters{
|
params := pathParameters{
|
||||||
pathParameter{Key: "testkey", Value: "testvalue"},
|
pathParameter{Key: "testkey", Value: "testvalue"},
|
||||||
pathParameter{Key: "testkey2", Value: "testvalue2"},
|
pathParameter{Key: "testkey2", Value: "testvalue2"},
|
||||||
|
@ -112,7 +112,7 @@ func TestContextParams(t *testing.T) {
|
||||||
expectedParamsStr := "param1=myparam1,param2=myparam2,param3=myparam3afterstatic,anything=/andhere/anything/you/like"
|
expectedParamsStr := "param1=myparam1,param2=myparam2,param3=myparam3afterstatic,anything=/andhere/anything/you/like"
|
||||||
iris.Get("/path/:param1/:param2/staticpath/:param3/*anything", func(ctx *iris.Context) {
|
iris.Get("/path/:param1/:param2/staticpath/:param3/*anything", func(ctx *iris.Context) {
|
||||||
paramsStr := ctx.ParamsSentence()
|
paramsStr := ctx.ParamsSentence()
|
||||||
ctx.Write(paramsStr)
|
ctx.WriteString(paramsStr)
|
||||||
})
|
})
|
||||||
|
|
||||||
httptest.New(iris.Default, t).GET("/path/myparam1/myparam2/staticpath/myparam3afterstatic/andhere/anything/you/like").Expect().Status(iris.StatusOK).Body().Equal(expectedParamsStr)
|
httptest.New(iris.Default, t).GET("/path/myparam1/myparam2/staticpath/myparam3afterstatic/andhere/anything/you/like").Expect().Status(iris.StatusOK).Body().Equal(expectedParamsStr)
|
||||||
|
@ -136,11 +136,11 @@ func TestContextHostString(t *testing.T) {
|
||||||
iris.ResetDefault()
|
iris.ResetDefault()
|
||||||
iris.Default.Config.VHost = "0.0.0.0:8080"
|
iris.Default.Config.VHost = "0.0.0.0:8080"
|
||||||
iris.Get("/", func(ctx *iris.Context) {
|
iris.Get("/", func(ctx *iris.Context) {
|
||||||
ctx.Write(ctx.HostString())
|
ctx.WriteString(ctx.Host())
|
||||||
})
|
})
|
||||||
|
|
||||||
iris.Get("/wrong", func(ctx *iris.Context) {
|
iris.Get("/wrong", func(ctx *iris.Context) {
|
||||||
ctx.Write(ctx.HostString() + "w")
|
ctx.WriteString(ctx.Host() + "w")
|
||||||
})
|
})
|
||||||
|
|
||||||
e := httptest.New(iris.Default, t)
|
e := httptest.New(iris.Default, t)
|
||||||
|
@ -155,11 +155,11 @@ func TestContextVirtualHostName(t *testing.T) {
|
||||||
vhost := "mycustomvirtualname.com"
|
vhost := "mycustomvirtualname.com"
|
||||||
iris.Default.Config.VHost = vhost + ":8080"
|
iris.Default.Config.VHost = vhost + ":8080"
|
||||||
iris.Get("/", func(ctx *iris.Context) {
|
iris.Get("/", func(ctx *iris.Context) {
|
||||||
ctx.Write(ctx.VirtualHostname())
|
ctx.WriteString(ctx.VirtualHostname())
|
||||||
})
|
})
|
||||||
|
|
||||||
iris.Get("/wrong", func(ctx *iris.Context) {
|
iris.Get("/wrong", func(ctx *iris.Context) {
|
||||||
ctx.Write(ctx.VirtualHostname() + "w")
|
ctx.WriteString(ctx.VirtualHostname() + "w")
|
||||||
})
|
})
|
||||||
|
|
||||||
e := httptest.New(iris.Default, t)
|
e := httptest.New(iris.Default, t)
|
||||||
|
@ -173,7 +173,7 @@ func TestContextFormValueString(t *testing.T) {
|
||||||
k = "postkey"
|
k = "postkey"
|
||||||
v = "postvalue"
|
v = "postvalue"
|
||||||
iris.Post("/", func(ctx *iris.Context) {
|
iris.Post("/", func(ctx *iris.Context) {
|
||||||
ctx.Write(k + "=" + ctx.FormValueString(k))
|
ctx.WriteString(k + "=" + ctx.FormValue(k))
|
||||||
})
|
})
|
||||||
e := httptest.New(iris.Default, t)
|
e := httptest.New(iris.Default, t)
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ func TestContextSubdomain(t *testing.T) {
|
||||||
//Default.Config.Tester.ListeningAddr = "mydomain.com:9999"
|
//Default.Config.Tester.ListeningAddr = "mydomain.com:9999"
|
||||||
// Default.Config.Tester.ExplicitURL = true
|
// Default.Config.Tester.ExplicitURL = true
|
||||||
iris.Party("mysubdomain.").Get("/mypath", func(ctx *iris.Context) {
|
iris.Party("mysubdomain.").Get("/mypath", func(ctx *iris.Context) {
|
||||||
ctx.Write(ctx.Subdomain())
|
ctx.WriteString(ctx.Subdomain())
|
||||||
})
|
})
|
||||||
|
|
||||||
e := httptest.New(iris.Default, t)
|
e := httptest.New(iris.Default, t)
|
||||||
|
@ -341,14 +341,14 @@ func TestContextReadForm(t *testing.T) {
|
||||||
// TestContextRedirectTo tests the named route redirect action
|
// TestContextRedirectTo tests the named route redirect action
|
||||||
func TestContextRedirectTo(t *testing.T) {
|
func TestContextRedirectTo(t *testing.T) {
|
||||||
iris.ResetDefault()
|
iris.ResetDefault()
|
||||||
h := func(ctx *iris.Context) { ctx.Write(ctx.PathString()) }
|
h := func(ctx *iris.Context) { ctx.WriteString(ctx.Path()) }
|
||||||
iris.Get("/mypath", h)("my-path")
|
iris.Get("/mypath", h)("my-path")
|
||||||
iris.Get("/mypostpath", h)("my-post-path")
|
iris.Get("/mypostpath", h)("my-post-path")
|
||||||
iris.Get("mypath/with/params/:param1/:param2", func(ctx *iris.Context) {
|
iris.Get("mypath/with/params/:param1/:param2", func(ctx *iris.Context) {
|
||||||
if l := ctx.ParamsLen(); l != 2 {
|
if l := ctx.ParamsLen(); l != 2 {
|
||||||
t.Fatalf("Strange error, expecting parameters to be two but we got: %d", l)
|
t.Fatalf("Strange error, expecting parameters to be two but we got: %d", l)
|
||||||
}
|
}
|
||||||
ctx.Write(ctx.PathString())
|
ctx.WriteString(ctx.Path())
|
||||||
})("my-path-with-params")
|
})("my-path-with-params")
|
||||||
|
|
||||||
iris.Get("/redirect/to/:routeName/*anyparams", func(ctx *iris.Context) {
|
iris.Get("/redirect/to/:routeName/*anyparams", func(ctx *iris.Context) {
|
||||||
|
@ -418,17 +418,16 @@ func TestContextCookieSetGetRemove(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
iris.Get("/set_advanced", func(ctx *iris.Context) {
|
iris.Get("/set_advanced", func(ctx *iris.Context) {
|
||||||
c := fasthttp.AcquireCookie()
|
c := &http.Cookie{}
|
||||||
c.SetKey(key)
|
c.Name = key
|
||||||
c.SetValue(value)
|
c.Value = value
|
||||||
c.SetHTTPOnly(true)
|
c.HttpOnly = true
|
||||||
c.SetExpire(time.Now().Add(time.Duration((60 * 60 * 24 * 7 * 4)) * time.Second))
|
c.Expires = time.Now().Add(time.Duration((60 * 60 * 24 * 7 * 4)) * time.Second)
|
||||||
ctx.SetCookie(c)
|
ctx.SetCookie(c)
|
||||||
fasthttp.ReleaseCookie(c)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
iris.Get("/get", func(ctx *iris.Context) {
|
iris.Get("/get", func(ctx *iris.Context) {
|
||||||
ctx.Write(ctx.GetCookie(key)) // should return my value
|
ctx.WriteString(ctx.GetCookie(key)) // should return my value
|
||||||
})
|
})
|
||||||
|
|
||||||
iris.Get("/remove", func(ctx *iris.Context) {
|
iris.Get("/remove", func(ctx *iris.Context) {
|
||||||
|
@ -440,7 +439,7 @@ func TestContextCookieSetGetRemove(t *testing.T) {
|
||||||
if cookieFound {
|
if cookieFound {
|
||||||
t.Fatalf("Cookie has been found, when it shouldn't!")
|
t.Fatalf("Cookie has been found, when it shouldn't!")
|
||||||
}
|
}
|
||||||
ctx.Write(ctx.GetCookie(key)) // should return ""
|
ctx.WriteString(ctx.GetCookie(key)) // should return ""
|
||||||
})
|
})
|
||||||
|
|
||||||
e := httptest.New(iris.Default, t)
|
e := httptest.New(iris.Default, t)
|
||||||
|
@ -453,130 +452,6 @@ func TestContextCookieSetGetRemove(t *testing.T) {
|
||||||
e.GET("/remove").Expect().Status(iris.StatusOK).Body().Equal("")
|
e.GET("/remove").Expect().Status(iris.StatusOK).Body().Equal("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextFlashMessages(t *testing.T) {
|
|
||||||
iris.ResetDefault()
|
|
||||||
firstKey := "name"
|
|
||||||
lastKey := "package"
|
|
||||||
|
|
||||||
values := pathParameters{pathParameter{Key: firstKey, Value: "kataras"}, pathParameter{Key: lastKey, Value: "iris"}}
|
|
||||||
jsonExpected := map[string]string{firstKey: "kataras", lastKey: "iris"}
|
|
||||||
// set the flashes, the cookies are filled
|
|
||||||
iris.Put("/set", func(ctx *iris.Context) {
|
|
||||||
for _, v := range values {
|
|
||||||
ctx.SetFlash(v.Key, v.Value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// get the first flash, the next should be available to the next requess
|
|
||||||
iris.Get("/get_first_flash", func(ctx *iris.Context) {
|
|
||||||
for _, v := range values {
|
|
||||||
val, err := ctx.GetFlash(v.Key)
|
|
||||||
if err == nil {
|
|
||||||
ctx.JSON(iris.StatusOK, map[string]string{v.Key: val})
|
|
||||||
} else {
|
|
||||||
ctx.JSON(iris.StatusOK, nil) // return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
// just an empty handler to test if the flashes should remeain to the next if GetFlash/GetFlashes used
|
|
||||||
iris.Get("/get_no_getflash", func(ctx *iris.Context) {
|
|
||||||
})
|
|
||||||
|
|
||||||
// get the last flash, the next should be available to the next requess
|
|
||||||
iris.Get("/get_last_flash", func(ctx *iris.Context) {
|
|
||||||
for i, v := range values {
|
|
||||||
if i == len(values)-1 {
|
|
||||||
val, err := ctx.GetFlash(v.Key)
|
|
||||||
if err == nil {
|
|
||||||
ctx.JSON(iris.StatusOK, map[string]string{v.Key: val})
|
|
||||||
} else {
|
|
||||||
ctx.JSON(iris.StatusOK, nil) // return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
iris.Get("/get_zero_flashes", func(ctx *iris.Context) {
|
|
||||||
ctx.JSON(iris.StatusOK, ctx.GetFlashes()) // should return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
// we use the GetFlash to get the flash messages, the messages and the cookies should be empty after that
|
|
||||||
iris.Get("/get_flash", func(ctx *iris.Context) {
|
|
||||||
kv := make(map[string]string)
|
|
||||||
for _, v := range values {
|
|
||||||
val, err := ctx.GetFlash(v.Key)
|
|
||||||
if err == nil {
|
|
||||||
kv[v.Key] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.JSON(iris.StatusOK, kv)
|
|
||||||
}, func(ctx *iris.Context) {
|
|
||||||
// at the same request, flashes should be available
|
|
||||||
if len(ctx.GetFlashes()) == 0 {
|
|
||||||
t.Fatalf("Flashes should be remeain to the whole request lifetime")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
iris.Get("/get_flashes", func(ctx *iris.Context) {
|
|
||||||
// one time one handler, using GetFlashes
|
|
||||||
kv := make(map[string]string)
|
|
||||||
flashes := ctx.GetFlashes()
|
|
||||||
//second time on the same handler, using the GetFlash
|
|
||||||
for k := range flashes {
|
|
||||||
kv[k], _ = ctx.GetFlash(k)
|
|
||||||
}
|
|
||||||
if len(flashes) != len(kv) {
|
|
||||||
ctx.SetStatusCode(iris.StatusNoContent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.Next()
|
|
||||||
|
|
||||||
}, func(ctx *iris.Context) {
|
|
||||||
// third time on a next handler
|
|
||||||
// test the if next handler has access to them(must) because flash are request lifetime now.
|
|
||||||
// print them to the client for test the response also
|
|
||||||
ctx.JSON(iris.StatusOK, ctx.GetFlashes())
|
|
||||||
})
|
|
||||||
|
|
||||||
e := httptest.New(iris.Default, t)
|
|
||||||
e.PUT("/set").Expect().Status(iris.StatusOK).Cookies().NotEmpty()
|
|
||||||
e.GET("/get_first_flash").Expect().Status(iris.StatusOK).JSON().Object().ContainsKey(firstKey).NotContainsKey(lastKey)
|
|
||||||
// just a request which does not use the flash message, so flash messages should be available on the next request
|
|
||||||
e.GET("/get_no_getflash").Expect().Status(iris.StatusOK)
|
|
||||||
e.GET("/get_last_flash").Expect().Status(iris.StatusOK).JSON().Object().ContainsKey(lastKey).NotContainsKey(firstKey)
|
|
||||||
g := e.GET("/get_zero_flashes").Expect().Status(iris.StatusOK)
|
|
||||||
g.JSON().Null()
|
|
||||||
g.Cookies().Empty()
|
|
||||||
// set the magain
|
|
||||||
e.PUT("/set").Expect().Status(iris.StatusOK).Cookies().NotEmpty()
|
|
||||||
// get them again using GetFlash
|
|
||||||
e.GET("/get_flash").Expect().Status(iris.StatusOK).JSON().Object().Equal(jsonExpected)
|
|
||||||
// this should be empty again
|
|
||||||
g = e.GET("/get_zero_flashes").Expect().Status(iris.StatusOK)
|
|
||||||
g.JSON().Null()
|
|
||||||
g.Cookies().Empty()
|
|
||||||
//set them again
|
|
||||||
e.PUT("/set").Expect().Status(iris.StatusOK).Cookies().NotEmpty()
|
|
||||||
// get them again using GetFlashes
|
|
||||||
e.GET("/get_flashes").Expect().Status(iris.StatusOK).JSON().Object().Equal(jsonExpected)
|
|
||||||
// this should be empty again
|
|
||||||
g = e.GET("/get_zero_flashes").Expect().Status(iris.StatusOK)
|
|
||||||
g.JSON().Null()
|
|
||||||
g.Cookies().Empty()
|
|
||||||
|
|
||||||
// test Get, and get again should return nothing
|
|
||||||
e.PUT("/set").Expect().Status(iris.StatusOK).Cookies().NotEmpty()
|
|
||||||
e.GET("/get_first_flash").Expect().Status(iris.StatusOK).JSON().Object().ContainsKey(firstKey).NotContainsKey(lastKey)
|
|
||||||
g = e.GET("/get_first_flash").Expect().Status(iris.StatusOK)
|
|
||||||
g.JSON().Null()
|
|
||||||
g.Cookies().Empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContextSessions(t *testing.T) {
|
func TestContextSessions(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
values := map[string]interface{}{
|
values := map[string]interface{}{
|
||||||
|
@ -770,9 +645,8 @@ func TestTemplatesDisabled(t *testing.T) {
|
||||||
iris.Default.Config.DisableTemplateEngines = true
|
iris.Default.Config.DisableTemplateEngines = true
|
||||||
|
|
||||||
file := "index.html"
|
file := "index.html"
|
||||||
ip := "0.0.0.0"
|
errTmpl := "<h2>Template: %s</h2><b>%s</b>"
|
||||||
errTmpl := "<h2>Template: %s\nIP: %s</h2><b>%s</b>"
|
expctedErrMsg := fmt.Sprintf(errTmpl, file, "Error: Unable to execute a template. Trace: Templates are disabled '.Config.DisableTemplatesEngines = true' please turn that to false, as defaulted.\n")
|
||||||
expctedErrMsg := fmt.Sprintf(errTmpl, file, ip, "Error: Unable to execute a template. Trace: Templates are disabled '.Config.DisableTemplatesEngines = true' please turn that to false, as defaulted.\n")
|
|
||||||
|
|
||||||
iris.Get("/renderErr", func(ctx *iris.Context) {
|
iris.Get("/renderErr", func(ctx *iris.Context) {
|
||||||
ctx.MustRender(file, nil)
|
ctx.MustRender(file, nil)
|
||||||
|
@ -788,46 +662,36 @@ func TestTransactions(t *testing.T) {
|
||||||
secondTransactionSuccessHTMLMessage := "<h1>This will sent at all cases because it lives on different transaction and it doesn't fails</h1>"
|
secondTransactionSuccessHTMLMessage := "<h1>This will sent at all cases because it lives on different transaction and it doesn't fails</h1>"
|
||||||
persistMessage := "<h1>I persist show this message to the client!</h1>"
|
persistMessage := "<h1>I persist show this message to the client!</h1>"
|
||||||
|
|
||||||
maybeFailureTransaction := func(shouldFail bool, isRequestScoped bool) func(scope *iris.TransactionScope) {
|
maybeFailureTransaction := func(shouldFail bool, isRequestScoped bool) func(t *iris.Transaction) {
|
||||||
return func(scope *iris.TransactionScope) {
|
return func(t *iris.Transaction) {
|
||||||
// OPTIONAl, if true then the next transactions will not be executed if this transaction fails
|
// OPTIONAl, the next transactions and the flow will not be skipped if this transaction fails
|
||||||
scope.RequestScoped(isRequestScoped)
|
if isRequestScoped {
|
||||||
|
t.SetScope(iris.RequestTransactionScope)
|
||||||
|
}
|
||||||
|
|
||||||
// OPTIONAL STEP:
|
// OPTIONAL STEP:
|
||||||
// create a new custom type of error here to keep track of the status code and reason message
|
// create a new custom type of error here to keep track of the status code and reason message
|
||||||
err := iris.NewErrWithStatus()
|
err := iris.NewTransactionErrResult()
|
||||||
|
|
||||||
// we should use scope.Context if we want to rollback on any errors lives inside this function clojure.
|
t.Context.Text(iris.StatusOK, "Blablabla this should not be sent to the client because we will fill the err with a message and status")
|
||||||
// if you want persistence then use the 'ctx'.
|
|
||||||
scope.Context.Text(iris.StatusOK, "Blablabla this should not be sent to the client because we will fill the err with a message and status")
|
|
||||||
|
|
||||||
// var firstErr error = do this() // your code here
|
|
||||||
// var secondErr error = try_do_this() // your code here
|
|
||||||
// var thirdErr error = try_do_this() // your code here
|
|
||||||
// var fail bool = false
|
|
||||||
|
|
||||||
// if firstErr != nil || secondErr != nil || thirdErr != nil {
|
|
||||||
// fail = true
|
|
||||||
// }
|
|
||||||
// or err.AppendReason(firstErr.Error()) // ... err.Reason(dbErr.Error()).Status(500)
|
|
||||||
|
|
||||||
fail := shouldFail
|
fail := shouldFail
|
||||||
|
|
||||||
if fail {
|
if fail {
|
||||||
err.Status(iris.StatusInternalServerError).
|
err.StatusCode = iris.StatusInternalServerError
|
||||||
// if status given but no reason then the default or the custom http error will be fired (like ctx.EmitError)
|
err.Reason = firstTransactionFailureMessage
|
||||||
Reason(firstTransactionFailureMessage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OPTIONAl STEP:
|
// OPTIONAl STEP:
|
||||||
// but useful if we want to post back an error message to the client if the transaction failed.
|
// but useful if we want to post back an error message to the client if the transaction failed.
|
||||||
// if the reason is empty then the transaction completed succesfuly,
|
// if the reason is empty then the transaction completed succesfuly,
|
||||||
// otherwise we rollback the whole response body and cookies and everything lives inside the scope.Request.
|
// otherwise we rollback the whole response body and cookies and everything lives inside the transaction.Request.
|
||||||
scope.Complete(err)
|
t.Complete(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
successTransaction := func(scope *iris.TransactionScope) {
|
successTransaction := func(scope *iris.Transaction) {
|
||||||
|
|
||||||
scope.Context.HTML(iris.StatusOK,
|
scope.Context.HTML(iris.StatusOK,
|
||||||
secondTransactionSuccessHTMLMessage)
|
secondTransactionSuccessHTMLMessage)
|
||||||
// * if we don't have any 'throw error' logic then no need of scope.Complete()
|
// * if we don't have any 'throw error' logic then no need of scope.Complete()
|
||||||
|
@ -850,19 +714,29 @@ func TestTransactions(t *testing.T) {
|
||||||
ctx.BeginTransaction(successTransaction)
|
ctx.BeginTransaction(successTransaction)
|
||||||
})
|
})
|
||||||
|
|
||||||
/*TODO: MAKE THIS TO WORK
|
|
||||||
iris.Get("/failFirsAndThirdTransactionsButSuccessSecond", func(ctx *iris.Context) {
|
|
||||||
ctx.BeginTransaction(maybeFailureTransaction(true, false))
|
|
||||||
ctx.BeginTransaction(successTransaction)
|
|
||||||
ctx.BeginTransaction(maybeFailureTransaction(true, false))
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
|
|
||||||
iris.Get("/failAllBecauseOfRequestScopeAndFailure", func(ctx *iris.Context) {
|
iris.Get("/failAllBecauseOfRequestScopeAndFailure", func(ctx *iris.Context) {
|
||||||
ctx.BeginTransaction(maybeFailureTransaction(true, true))
|
ctx.BeginTransaction(maybeFailureTransaction(true, true))
|
||||||
ctx.BeginTransaction(successTransaction)
|
ctx.BeginTransaction(successTransaction)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
customErrorTemplateText := "<h1>custom error</h1>"
|
||||||
|
iris.OnError(iris.StatusInternalServerError, func(ctx *iris.Context) {
|
||||||
|
ctx.Text(iris.StatusInternalServerError, customErrorTemplateText)
|
||||||
|
})
|
||||||
|
|
||||||
|
failureWithRegisteredErrorHandler := func(ctx *iris.Context) {
|
||||||
|
ctx.BeginTransaction(func(transaction *iris.Transaction) {
|
||||||
|
transaction.SetScope(iris.RequestTransactionScope)
|
||||||
|
err := iris.NewTransactionErrResult()
|
||||||
|
err.StatusCode = iris.StatusInternalServerError // set only the status code in order to execute the registered template
|
||||||
|
transaction.Complete(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.Text(iris.StatusOK, "this will not be sent to the client because first is requested scope and it's failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
iris.Get("/failAllBecauseFirstTransactionFailedWithRegisteredErrorTemplate", failureWithRegisteredErrorHandler)
|
||||||
|
|
||||||
e := httptest.New(iris.Default, t)
|
e := httptest.New(iris.Default, t)
|
||||||
|
|
||||||
e.GET("/failFirsTransactionButSuccessSecondWithPersistMessage").
|
e.GET("/failFirsTransactionButSuccessSecondWithPersistMessage").
|
||||||
|
@ -870,188 +744,24 @@ func TestTransactions(t *testing.T) {
|
||||||
Status(iris.StatusOK).
|
Status(iris.StatusOK).
|
||||||
ContentType("text/html", iris.Config.Charset).
|
ContentType("text/html", iris.Config.Charset).
|
||||||
Body().
|
Body().
|
||||||
Equal(firstTransactionFailureMessage + secondTransactionSuccessHTMLMessage + persistMessage)
|
Equal(secondTransactionSuccessHTMLMessage + persistMessage)
|
||||||
|
|
||||||
e.GET("/failFirsTransactionButSuccessSecond").
|
e.GET("/failFirsTransactionButSuccessSecond").
|
||||||
Expect().
|
Expect().
|
||||||
Status(iris.StatusOK).
|
Status(iris.StatusOK).
|
||||||
ContentType("text/html", iris.Config.Charset).
|
ContentType("text/html", iris.Config.Charset).
|
||||||
Body().
|
Body().
|
||||||
Equal(firstTransactionFailureMessage + secondTransactionSuccessHTMLMessage)
|
Equal(secondTransactionSuccessHTMLMessage)
|
||||||
/*
|
|
||||||
e.GET("/failFirsAndThirdTransactionsButSuccessSecond").
|
|
||||||
Expect().
|
|
||||||
Status(iris.StatusOK).
|
|
||||||
ContentType("text/html", iris.Config.Charset).
|
|
||||||
Body().
|
|
||||||
Equal(firstTransactionFailureMessage + secondTransactionSuccessHTMLMessage)
|
|
||||||
*/
|
|
||||||
|
|
||||||
e.GET("/failAllBecauseOfRequestScopeAndFailure").
|
e.GET("/failAllBecauseOfRequestScopeAndFailure").
|
||||||
Expect().
|
Expect().
|
||||||
Status(iris.StatusInternalServerError).
|
Status(iris.StatusInternalServerError).
|
||||||
Body().
|
Body().
|
||||||
Equal(firstTransactionFailureMessage)
|
Equal(firstTransactionFailureMessage)
|
||||||
}
|
|
||||||
|
e.GET("/failAllBecauseFirstTransactionFailedWithRegisteredErrorTemplate").
|
||||||
func TestTransactionsMiddleware(t *testing.T) {
|
Expect().
|
||||||
forbiddenMsg := "Error: Not allowed."
|
Status(iris.StatusInternalServerError).
|
||||||
allowMsg := "Hello!"
|
Body().
|
||||||
|
Equal(customErrorTemplateText)
|
||||||
transaction := iris.TransactionFunc(func(scope *iris.TransactionScope) {
|
|
||||||
// must set that to true when we want to bypass the whole handler if this transaction fails.
|
|
||||||
scope.RequestScoped(true)
|
|
||||||
// optional but useful when we want a specific reason message
|
|
||||||
// without register global custom http errors to a status (using iris.OnError)
|
|
||||||
err := iris.NewErrWithStatus()
|
|
||||||
// the difference from ctx.BeginTransaction is that
|
|
||||||
// if that fails it not only skips all transactions but all next handler(s) too
|
|
||||||
// here we use this middleware AFTER a handler, so all handlers are executed before that but
|
|
||||||
// this will fail because this is the difference from normal handler, it resets the whole response if Complete(notEmptyError)
|
|
||||||
if scope.Context.GetString("username") != "iris" {
|
|
||||||
err.Status(iris.StatusForbidden).Reason(forbiddenMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.Complete(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
failHandlerFunc := func(ctx *iris.Context) {
|
|
||||||
ctx.Set("username", "wrong")
|
|
||||||
ctx.Write("This should not be sent to the client.")
|
|
||||||
|
|
||||||
ctx.Next() // in order to execute the next handler, which is a wrapper of transaction
|
|
||||||
}
|
|
||||||
|
|
||||||
successHandlerFunc := func(ctx *iris.Context) {
|
|
||||||
ctx.Set("username", "iris")
|
|
||||||
ctx.Write("Hello!")
|
|
||||||
|
|
||||||
ctx.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// per route after transaction(middleware)
|
|
||||||
api := iris.New()
|
|
||||||
api.Get("/transaction_after_route_middleware_fail_because_of_request_scope_fails", failHandlerFunc, transaction.ToMiddleware()) // after per route
|
|
||||||
|
|
||||||
api.Get("/transaction_after_route_middleware_success_so_response_should_be_sent_to_the_client", successHandlerFunc, transaction.ToMiddleware()) // after per route
|
|
||||||
|
|
||||||
e := httptest.New(api, t)
|
|
||||||
e.GET("/transaction_after_route_middleware_fail_because_of_request_scope_fails").
|
|
||||||
Expect().
|
|
||||||
Status(iris.StatusForbidden).
|
|
||||||
Body().
|
|
||||||
Equal(forbiddenMsg)
|
|
||||||
|
|
||||||
e.GET("/transaction_after_route_middleware_success_so_response_should_be_sent_to_the_client").
|
|
||||||
Expect().
|
|
||||||
Status(iris.StatusOK).
|
|
||||||
Body().
|
|
||||||
Equal(allowMsg)
|
|
||||||
|
|
||||||
// global, after all route's handlers
|
|
||||||
api = iris.New()
|
|
||||||
|
|
||||||
api.DoneTransaction(transaction)
|
|
||||||
api.Get("/failed_because_of_done_transaction", failHandlerFunc)
|
|
||||||
|
|
||||||
api.Get("/succeed_because_of_done_transaction", successHandlerFunc)
|
|
||||||
|
|
||||||
e = httptest.New(api, t)
|
|
||||||
e.GET("/failed_because_of_done_transaction").
|
|
||||||
Expect().
|
|
||||||
Status(iris.StatusForbidden).
|
|
||||||
Body().
|
|
||||||
Equal(forbiddenMsg)
|
|
||||||
|
|
||||||
e.GET("/succeed_because_of_done_transaction").
|
|
||||||
Expect().
|
|
||||||
Status(iris.StatusOK).
|
|
||||||
Body().
|
|
||||||
Equal(allowMsg)
|
|
||||||
|
|
||||||
// global, before all route's handlers transaction, this is not so useful so these transaction will be succesfuly and just adds a message
|
|
||||||
api = iris.New()
|
|
||||||
transactionHTMLResponse := "<b>Transaction here</b>"
|
|
||||||
expectedResponse := transactionHTMLResponse + allowMsg
|
|
||||||
api.UseTransaction(func(scope *iris.TransactionScope) {
|
|
||||||
scope.Context.HTML(iris.StatusOK, transactionHTMLResponse)
|
|
||||||
// scope.Context.Next() is automatically called on UseTransaction
|
|
||||||
})
|
|
||||||
|
|
||||||
api.Get("/route1", func(ctx *iris.Context) {
|
|
||||||
ctx.Write(allowMsg)
|
|
||||||
})
|
|
||||||
|
|
||||||
e = httptest.New(api, t)
|
|
||||||
e.GET("/route1").
|
|
||||||
Expect().
|
|
||||||
Status(iris.StatusOK).
|
|
||||||
ContentType("text/html", api.Config.Charset).
|
|
||||||
Body().
|
|
||||||
Equal(expectedResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTransactionFailureCompletionButSilently(t *testing.T) {
|
|
||||||
iris.ResetDefault()
|
|
||||||
expectedBody := "I don't care for any unexpected panics, this response should be sent."
|
|
||||||
|
|
||||||
iris.Get("/panic_silent", func(ctx *iris.Context) {
|
|
||||||
ctx.BeginTransaction(func(scope *iris.TransactionScope) {
|
|
||||||
scope.Context.Write("blablabla this should not be shown because of 'unexpected' panic.")
|
|
||||||
panic("OMG, UNEXPECTED ERROR BECAUSE YOU ARE NOT A DISCIPLINED PROGRAMMER, BUT IRIS HAS YOU COVERED!")
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx.WriteString(expectedBody)
|
|
||||||
})
|
|
||||||
|
|
||||||
iris.Get("/expected_error_but_silent_instead_of_send_the_reason", func(ctx *iris.Context) {
|
|
||||||
ctx.BeginTransaction(func(scope *iris.TransactionScope) {
|
|
||||||
scope.Context.Write("this will not be sent.")
|
|
||||||
// complete with a failure ( so revert the changes) but do it silently.
|
|
||||||
scope.Complete(iris.NewErrFallback())
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx.WriteString(expectedBody)
|
|
||||||
})
|
|
||||||
|
|
||||||
iris.Get("/silly_way_expected_error_but_silent_instead_of_send_the_reason", func(ctx *iris.Context) {
|
|
||||||
ctx.BeginTransaction(func(scope *iris.TransactionScope) {
|
|
||||||
scope.Context.Write("this will not be sent.")
|
|
||||||
|
|
||||||
// or if you know the error will be silent from the beggining: err := &iris.ErrFallback{}
|
|
||||||
err := iris.NewErrWithStatus()
|
|
||||||
|
|
||||||
fail := true
|
|
||||||
|
|
||||||
if fail {
|
|
||||||
err.Status(iris.StatusBadRequest).Reason("we dont know but it was expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// we change our mind we don't want to send the error to the user, so err.Silent to the .Complete
|
|
||||||
// complete with a failure ( so revert the changes) but do it silently.
|
|
||||||
scope.Complete(err.Silent())
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx.WriteString(expectedBody)
|
|
||||||
})
|
|
||||||
|
|
||||||
e := httptest.New(iris.Default, t)
|
|
||||||
|
|
||||||
e.GET("/panic_silent").Expect().
|
|
||||||
Status(iris.StatusOK).
|
|
||||||
Body().
|
|
||||||
Equal(expectedBody)
|
|
||||||
|
|
||||||
e.GET("/expected_error_but_silent_instead_of_send_the_reason").
|
|
||||||
Expect().
|
|
||||||
Status(iris.StatusOK).
|
|
||||||
Body().
|
|
||||||
Equal(expectedBody)
|
|
||||||
|
|
||||||
e.GET("/silly_way_expected_error_but_silent_instead_of_send_the_reason").
|
|
||||||
Expect().
|
|
||||||
Status(iris.StatusOK).
|
|
||||||
Body().
|
|
||||||
Equal(expectedBody)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
442
http.go
442
http.go
|
@ -1,7 +1,6 @@
|
||||||
package iris
|
package iris
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
@ -16,8 +15,6 @@ import (
|
||||||
"github.com/geekypanda/httpcache"
|
"github.com/geekypanda/httpcache"
|
||||||
"github.com/iris-contrib/letsencrypt"
|
"github.com/iris-contrib/letsencrypt"
|
||||||
"github.com/kataras/go-errors"
|
"github.com/kataras/go-errors"
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
"github.com/valyala/fasthttp/fasthttpadaptor"
|
|
||||||
"golang.org/x/crypto/acme/autocert"
|
"golang.org/x/crypto/acme/autocert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,171 +42,139 @@ const (
|
||||||
var (
|
var (
|
||||||
// AllMethods "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", "PATCH", "OPTIONS", "TRACE"
|
// AllMethods "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", "PATCH", "OPTIONS", "TRACE"
|
||||||
AllMethods = [...]string{MethodGet, MethodPost, MethodPut, MethodDelete, MethodConnect, MethodHead, MethodPatch, MethodOptions, MethodTrace}
|
AllMethods = [...]string{MethodGet, MethodPost, MethodPut, MethodDelete, MethodConnect, MethodHead, MethodPatch, MethodOptions, MethodTrace}
|
||||||
|
|
||||||
/* methods as []byte, these are really used by iris */
|
|
||||||
|
|
||||||
// MethodGetBytes "GET"
|
|
||||||
MethodGetBytes = []byte(MethodGet)
|
|
||||||
// MethodPostBytes "POST"
|
|
||||||
MethodPostBytes = []byte(MethodPost)
|
|
||||||
// MethodPutBytes "PUT"
|
|
||||||
MethodPutBytes = []byte(MethodPut)
|
|
||||||
// MethodDeleteBytes "DELETE"
|
|
||||||
MethodDeleteBytes = []byte(MethodDelete)
|
|
||||||
// MethodConnectBytes "CONNECT"
|
|
||||||
MethodConnectBytes = []byte(MethodConnect)
|
|
||||||
// MethodHeadBytes "HEAD"
|
|
||||||
MethodHeadBytes = []byte(MethodHead)
|
|
||||||
// MethodPatchBytes "PATCH"
|
|
||||||
MethodPatchBytes = []byte(MethodPatch)
|
|
||||||
// MethodOptionsBytes "OPTIONS"
|
|
||||||
MethodOptionsBytes = []byte(MethodOptions)
|
|
||||||
// MethodTraceBytes "TRACE"
|
|
||||||
MethodTraceBytes = []byte(MethodTrace)
|
|
||||||
/* */
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// HTTP status codes.
|
||||||
const (
|
const (
|
||||||
// StatusContinue http status '100'
|
StatusContinue = 100 // RFC 7231, 6.2.1
|
||||||
StatusContinue = 100
|
StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
|
||||||
// StatusSwitchingProtocols http status '101'
|
StatusProcessing = 102 // RFC 2518, 10.1
|
||||||
StatusSwitchingProtocols = 101
|
|
||||||
// StatusOK http status '200'
|
StatusOK = 200 // RFC 7231, 6.3.1
|
||||||
StatusOK = 200
|
StatusCreated = 201 // RFC 7231, 6.3.2
|
||||||
// StatusCreated http status '201'
|
StatusAccepted = 202 // RFC 7231, 6.3.3
|
||||||
StatusCreated = 201
|
StatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4
|
||||||
// StatusAccepted http status '202'
|
StatusNoContent = 204 // RFC 7231, 6.3.5
|
||||||
StatusAccepted = 202
|
StatusResetContent = 205 // RFC 7231, 6.3.6
|
||||||
// StatusNonAuthoritativeInfo http status '203'
|
StatusPartialContent = 206 // RFC 7233, 4.1
|
||||||
StatusNonAuthoritativeInfo = 203
|
StatusMultiStatus = 207 // RFC 4918, 11.1
|
||||||
// StatusNoContent http status '204'
|
StatusAlreadyReported = 208 // RFC 5842, 7.1
|
||||||
StatusNoContent = 204
|
StatusIMUsed = 226 // RFC 3229, 10.4.1
|
||||||
// StatusResetContent http status '205'
|
|
||||||
StatusResetContent = 205
|
StatusMultipleChoices = 300 // RFC 7231, 6.4.1
|
||||||
// StatusPartialContent http status '206'
|
StatusMovedPermanently = 301 // RFC 7231, 6.4.2
|
||||||
StatusPartialContent = 206
|
StatusFound = 302 // RFC 7231, 6.4.3
|
||||||
// StatusMultipleChoices http status '300'
|
StatusSeeOther = 303 // RFC 7231, 6.4.4
|
||||||
StatusMultipleChoices = 300
|
StatusNotModified = 304 // RFC 7232, 4.1
|
||||||
// StatusMovedPermanently http status '301'
|
StatusUseProxy = 305 // RFC 7231, 6.4.5
|
||||||
StatusMovedPermanently = 301
|
_ = 306 // RFC 7231, 6.4.6 (Unused)
|
||||||
// StatusFound http status '302'
|
StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7
|
||||||
StatusFound = 302
|
StatusPermanentRedirect = 308 // RFC 7538, 3
|
||||||
// StatusSeeOther http status '303'
|
|
||||||
StatusSeeOther = 303
|
StatusBadRequest = 400 // RFC 7231, 6.5.1
|
||||||
// StatusNotModified http status '304'
|
StatusUnauthorized = 401 // RFC 7235, 3.1
|
||||||
StatusNotModified = 304
|
StatusPaymentRequired = 402 // RFC 7231, 6.5.2
|
||||||
// StatusUseProxy http status '305'
|
StatusForbidden = 403 // RFC 7231, 6.5.3
|
||||||
StatusUseProxy = 305
|
StatusNotFound = 404 // RFC 7231, 6.5.4
|
||||||
// StatusTemporaryRedirect http status '307'
|
StatusMethodNotAllowed = 405 // RFC 7231, 6.5.5
|
||||||
StatusTemporaryRedirect = 307
|
StatusNotAcceptable = 406 // RFC 7231, 6.5.6
|
||||||
// StatusBadRequest http status '400'
|
StatusProxyAuthRequired = 407 // RFC 7235, 3.2
|
||||||
StatusBadRequest = 400
|
StatusRequestTimeout = 408 // RFC 7231, 6.5.7
|
||||||
// StatusUnauthorized http status '401'
|
StatusConflict = 409 // RFC 7231, 6.5.8
|
||||||
StatusUnauthorized = 401
|
StatusGone = 410 // RFC 7231, 6.5.9
|
||||||
// StatusPaymentRequired http status '402'
|
StatusLengthRequired = 411 // RFC 7231, 6.5.10
|
||||||
StatusPaymentRequired = 402
|
StatusPreconditionFailed = 412 // RFC 7232, 4.2
|
||||||
// StatusForbidden http status '403'
|
StatusRequestEntityTooLarge = 413 // RFC 7231, 6.5.11
|
||||||
StatusForbidden = 403
|
StatusRequestURITooLong = 414 // RFC 7231, 6.5.12
|
||||||
// StatusNotFound http status '404'
|
StatusUnsupportedMediaType = 415 // RFC 7231, 6.5.13
|
||||||
StatusNotFound = 404
|
StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4
|
||||||
// StatusMethodNotAllowed http status '405'
|
StatusExpectationFailed = 417 // RFC 7231, 6.5.14
|
||||||
StatusMethodNotAllowed = 405
|
StatusTeapot = 418 // RFC 7168, 2.3.3
|
||||||
// StatusNotAcceptable http status '406'
|
StatusUnprocessableEntity = 422 // RFC 4918, 11.2
|
||||||
StatusNotAcceptable = 406
|
StatusLocked = 423 // RFC 4918, 11.3
|
||||||
// StatusProxyAuthRequired http status '407'
|
StatusFailedDependency = 424 // RFC 4918, 11.4
|
||||||
StatusProxyAuthRequired = 407
|
StatusUpgradeRequired = 426 // RFC 7231, 6.5.15
|
||||||
// StatusRequestTimeout http status '408'
|
StatusPreconditionRequired = 428 // RFC 6585, 3
|
||||||
StatusRequestTimeout = 408
|
StatusTooManyRequests = 429 // RFC 6585, 4
|
||||||
// StatusConflict http status '409'
|
StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5
|
||||||
StatusConflict = 409
|
StatusUnavailableForLegalReasons = 451 // RFC 7725, 3
|
||||||
// StatusGone http status '410'
|
|
||||||
StatusGone = 410
|
StatusInternalServerError = 500 // RFC 7231, 6.6.1
|
||||||
// StatusLengthRequired http status '411'
|
StatusNotImplemented = 501 // RFC 7231, 6.6.2
|
||||||
StatusLengthRequired = 411
|
StatusBadGateway = 502 // RFC 7231, 6.6.3
|
||||||
// StatusPreconditionFailed http status '412'
|
StatusServiceUnavailable = 503 // RFC 7231, 6.6.4
|
||||||
StatusPreconditionFailed = 412
|
StatusGatewayTimeout = 504 // RFC 7231, 6.6.5
|
||||||
// StatusRequestEntityTooLarge http status '413'
|
StatusHTTPVersionNotSupported = 505 // RFC 7231, 6.6.6
|
||||||
StatusRequestEntityTooLarge = 413
|
StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1
|
||||||
// StatusRequestURITooLong http status '414'
|
StatusInsufficientStorage = 507 // RFC 4918, 11.5
|
||||||
StatusRequestURITooLong = 414
|
StatusLoopDetected = 508 // RFC 5842, 7.2
|
||||||
// StatusUnsupportedMediaType http status '415'
|
StatusNotExtended = 510 // RFC 2774, 7
|
||||||
StatusUnsupportedMediaType = 415
|
StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
|
||||||
// StatusRequestedRangeNotSatisfiable http status '416'
|
|
||||||
StatusRequestedRangeNotSatisfiable = 416
|
|
||||||
// StatusExpectationFailed http status '417'
|
|
||||||
StatusExpectationFailed = 417
|
|
||||||
// StatusTeapot http status '418'
|
|
||||||
StatusTeapot = 418
|
|
||||||
// StatusPreconditionRequired http status '428'
|
|
||||||
StatusPreconditionRequired = 428
|
|
||||||
// StatusTooManyRequests http status '429'
|
|
||||||
StatusTooManyRequests = 429
|
|
||||||
// StatusRequestHeaderFieldsTooLarge http status '431'
|
|
||||||
StatusRequestHeaderFieldsTooLarge = 431
|
|
||||||
// StatusUnavailableForLegalReasons http status '451'
|
|
||||||
StatusUnavailableForLegalReasons = 451
|
|
||||||
// StatusInternalServerError http status '500'
|
|
||||||
StatusInternalServerError = 500
|
|
||||||
// StatusNotImplemented http status '501'
|
|
||||||
StatusNotImplemented = 501
|
|
||||||
// StatusBadGateway http status '502'
|
|
||||||
StatusBadGateway = 502
|
|
||||||
// StatusServiceUnavailable http status '503'
|
|
||||||
StatusServiceUnavailable = 503
|
|
||||||
// StatusGatewayTimeout http status '504'
|
|
||||||
StatusGatewayTimeout = 504
|
|
||||||
// StatusHTTPVersionNotSupported http status '505'
|
|
||||||
StatusHTTPVersionNotSupported = 505
|
|
||||||
// StatusNetworkAuthenticationRequired http status '511'
|
|
||||||
StatusNetworkAuthenticationRequired = 511
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var statusText = map[int]string{
|
var statusText = map[int]string{
|
||||||
StatusContinue: "Continue",
|
StatusContinue: "Continue",
|
||||||
StatusSwitchingProtocols: "Switching Protocols",
|
StatusSwitchingProtocols: "Switching Protocols",
|
||||||
StatusOK: "OK",
|
StatusProcessing: "Processing",
|
||||||
StatusCreated: "Created",
|
|
||||||
StatusAccepted: "Accepted",
|
StatusOK: "OK",
|
||||||
StatusNonAuthoritativeInfo: "Non-Authoritative Information",
|
StatusCreated: "Created",
|
||||||
StatusNoContent: "No Content",
|
StatusAccepted: "Accepted",
|
||||||
StatusResetContent: "Reset Content",
|
StatusNonAuthoritativeInfo: "Non-Authoritative Information",
|
||||||
StatusPartialContent: "Partial Content",
|
StatusNoContent: "No Content",
|
||||||
StatusMultipleChoices: "Multiple Choices",
|
StatusResetContent: "Reset Content",
|
||||||
StatusMovedPermanently: "Moved Permanently",
|
StatusPartialContent: "Partial Content",
|
||||||
StatusFound: "Found",
|
StatusMultiStatus: "Multi-Status",
|
||||||
StatusSeeOther: "See Other",
|
StatusAlreadyReported: "Already Reported",
|
||||||
StatusNotModified: "Not Modified",
|
StatusIMUsed: "IM Used",
|
||||||
StatusUseProxy: "Use Proxy",
|
|
||||||
StatusTemporaryRedirect: "Temporary Redirect",
|
StatusMultipleChoices: "Multiple Choices",
|
||||||
StatusBadRequest: "Bad Request",
|
StatusMovedPermanently: "Moved Permanently",
|
||||||
StatusUnauthorized: "Unauthorized",
|
StatusFound: "Found",
|
||||||
StatusPaymentRequired: "Payment Required",
|
StatusSeeOther: "See Other",
|
||||||
StatusForbidden: "Forbidden",
|
StatusNotModified: "Not Modified",
|
||||||
StatusNotFound: "Not Found",
|
StatusUseProxy: "Use Proxy",
|
||||||
StatusMethodNotAllowed: "Method Not Allowed",
|
StatusTemporaryRedirect: "Temporary Redirect",
|
||||||
StatusNotAcceptable: "Not Acceptable",
|
StatusPermanentRedirect: "Permanent Redirect",
|
||||||
StatusProxyAuthRequired: "Proxy Authentication Required",
|
|
||||||
StatusRequestTimeout: "Request Timeout",
|
StatusBadRequest: "Bad Request",
|
||||||
StatusConflict: "Conflict",
|
StatusUnauthorized: "Unauthorized",
|
||||||
StatusGone: "Gone",
|
StatusPaymentRequired: "Payment Required",
|
||||||
StatusLengthRequired: "Length Required",
|
StatusForbidden: "Forbidden",
|
||||||
StatusPreconditionFailed: "Precondition Failed",
|
StatusNotFound: "Not Found",
|
||||||
StatusRequestEntityTooLarge: "Request Entity Too Large",
|
StatusMethodNotAllowed: "Method Not Allowed",
|
||||||
StatusRequestURITooLong: "Request URI Too Long",
|
StatusNotAcceptable: "Not Acceptable",
|
||||||
StatusUnsupportedMediaType: "Unsupported Media Type",
|
StatusProxyAuthRequired: "Proxy Authentication Required",
|
||||||
StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
|
StatusRequestTimeout: "Request Timeout",
|
||||||
StatusExpectationFailed: "Expectation Failed",
|
StatusConflict: "Conflict",
|
||||||
StatusTeapot: "I'm a teapot",
|
StatusGone: "Gone",
|
||||||
StatusPreconditionRequired: "Precondition Required",
|
StatusLengthRequired: "Length Required",
|
||||||
StatusTooManyRequests: "Too Many Requests",
|
StatusPreconditionFailed: "Precondition Failed",
|
||||||
StatusRequestHeaderFieldsTooLarge: "Request Header Fields Too Large",
|
StatusRequestEntityTooLarge: "Request Entity Too Large",
|
||||||
StatusUnavailableForLegalReasons: "Unavailable For Legal Reasons",
|
StatusRequestURITooLong: "Request URI Too Long",
|
||||||
|
StatusUnsupportedMediaType: "Unsupported Media Type",
|
||||||
|
StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
|
||||||
|
StatusExpectationFailed: "Expectation Failed",
|
||||||
|
StatusTeapot: "I'm a teapot",
|
||||||
|
StatusUnprocessableEntity: "Unprocessable Entity",
|
||||||
|
StatusLocked: "Locked",
|
||||||
|
StatusFailedDependency: "Failed Dependency",
|
||||||
|
StatusUpgradeRequired: "Upgrade Required",
|
||||||
|
StatusPreconditionRequired: "Precondition Required",
|
||||||
|
StatusTooManyRequests: "Too Many Requests",
|
||||||
|
StatusRequestHeaderFieldsTooLarge: "Request Header Fields Too Large",
|
||||||
|
StatusUnavailableForLegalReasons: "Unavailable For Legal Reasons",
|
||||||
|
|
||||||
StatusInternalServerError: "Internal Server Error",
|
StatusInternalServerError: "Internal Server Error",
|
||||||
StatusNotImplemented: "Not Implemented",
|
StatusNotImplemented: "Not Implemented",
|
||||||
StatusBadGateway: "Bad Gateway",
|
StatusBadGateway: "Bad Gateway",
|
||||||
StatusServiceUnavailable: "Service Unavailable",
|
StatusServiceUnavailable: "Service Unavailable",
|
||||||
StatusGatewayTimeout: "Gateway Timeout",
|
StatusGatewayTimeout: "Gateway Timeout",
|
||||||
StatusHTTPVersionNotSupported: "HTTP Version Not Supported",
|
StatusHTTPVersionNotSupported: "HTTP Version Not Supported",
|
||||||
|
StatusVariantAlsoNegotiates: "Variant Also Negotiates",
|
||||||
|
StatusInsufficientStorage: "Insufficient Storage",
|
||||||
|
StatusLoopDetected: "Loop Detected",
|
||||||
|
StatusNotExtended: "Not Extended",
|
||||||
StatusNetworkAuthenticationRequired: "Network Authentication Required",
|
StatusNetworkAuthenticationRequired: "Network Authentication Required",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,7 +191,7 @@ var errHandler = errors.New("Passed argument is not func(*Context) neither an ob
|
||||||
type (
|
type (
|
||||||
// Handler the main Iris Handler interface.
|
// Handler the main Iris Handler interface.
|
||||||
Handler interface {
|
Handler interface {
|
||||||
Serve(ctx *Context)
|
Serve(ctx *Context) // iris-specific
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlerFunc type is an adapter to allow the use of
|
// HandlerFunc type is an adapter to allow the use of
|
||||||
|
@ -246,37 +211,36 @@ func (h HandlerFunc) Serve(ctx *Context) {
|
||||||
h(ctx)
|
h(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToHandler converts an http.Handler or http.HandlerFunc to an iris.Handler
|
// ToNativeHandler converts an iris handler to http.Handler
|
||||||
func ToHandler(handler interface{}) Handler {
|
func ToNativeHandler(station *Framework, h Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := station.AcquireCtx(w, r)
|
||||||
|
h.Serve(ctx)
|
||||||
|
station.ReleaseCtx(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToHandler converts an http.Handler/HandlerFunc to iris.Handler(Func)
|
||||||
|
func ToHandler(handler interface{}) HandlerFunc {
|
||||||
|
|
||||||
//this is not the best way to do it, but I dont have any options right now.
|
//this is not the best way to do it, but I dont have any options right now.
|
||||||
switch handler.(type) {
|
switch handler.(type) {
|
||||||
case Handler:
|
case HandlerFunc:
|
||||||
//it's already an iris handler
|
//it's already an iris handler
|
||||||
return handler.(Handler)
|
return handler.(HandlerFunc)
|
||||||
case http.Handler:
|
case http.Handler:
|
||||||
//it's http.Handler
|
//it's http.Handler
|
||||||
h := fasthttpadaptor.NewFastHTTPHandlerFunc(handler.(http.Handler).ServeHTTP)
|
h := handler.(http.Handler)
|
||||||
|
return func(ctx *Context) {
|
||||||
return ToHandlerFastHTTP(h)
|
h.ServeHTTP(ctx.ResponseWriter, ctx.Request)
|
||||||
|
}
|
||||||
case func(http.ResponseWriter, *http.Request):
|
case func(http.ResponseWriter, *http.Request):
|
||||||
//it's http.HandlerFunc
|
return ToHandler(http.HandlerFunc(handler.(func(http.ResponseWriter, *http.Request))))
|
||||||
h := fasthttpadaptor.NewFastHTTPHandlerFunc(handler.(func(http.ResponseWriter, *http.Request)))
|
//for func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc): READ iris-conrib/middleware/README.md for details
|
||||||
return ToHandlerFastHTTP(h)
|
|
||||||
default:
|
default:
|
||||||
panic(errHandler.Format(handler, handler))
|
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 just make []HandlerFunc to []Handler, although HandlerFunc and Handler are the same
|
// convertToHandlers just make []HandlerFunc to []Handler, although HandlerFunc and Handler are the same
|
||||||
|
@ -700,24 +664,24 @@ func (e *muxEntry) precedenceTo(index int) int {
|
||||||
// it seems useless but I prefer to keep the cached handler on its own memory stack,
|
// it seems useless but I prefer to keep the cached handler on its own memory stack,
|
||||||
// reason: no clojures hell in the Cache function
|
// reason: no clojures hell in the Cache function
|
||||||
type cachedMuxEntry struct {
|
type cachedMuxEntry struct {
|
||||||
cachedHandler fasthttp.RequestHandler
|
cachedHandler http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCachedMuxEntry(f *Framework, bodyHandler HandlerFunc, expiration time.Duration) *cachedMuxEntry {
|
func newCachedMuxEntry(s *Framework, bodyHandler HandlerFunc, expiration time.Duration) *cachedMuxEntry {
|
||||||
fhandler := func(reqCtx *fasthttp.RequestCtx) {
|
httphandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := f.AcquireCtx(reqCtx)
|
ctx := s.AcquireCtx(w, r)
|
||||||
bodyHandler.Serve(ctx)
|
bodyHandler.Serve(ctx)
|
||||||
f.ReleaseCtx(ctx)
|
s.ReleaseCtx(ctx)
|
||||||
}
|
})
|
||||||
|
|
||||||
cachedHandler := httpcache.CacheFasthttpFunc(fhandler, expiration)
|
cachedHandler := httpcache.Cache(httphandler, expiration)
|
||||||
return &cachedMuxEntry{
|
return &cachedMuxEntry{
|
||||||
cachedHandler: cachedHandler,
|
cachedHandler: cachedHandler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cachedMuxEntry) Serve(ctx *Context) {
|
func (c *cachedMuxEntry) Serve(ctx *Context) {
|
||||||
c.cachedHandler(ctx.RequestCtx)
|
c.cachedHandler.ServeHTTP(ctx.ResponseWriter, ctx.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -743,8 +707,7 @@ type (
|
||||||
// if no name given then it's the subdomain+path
|
// if no name given then it's the subdomain+path
|
||||||
name string
|
name string
|
||||||
subdomain string
|
subdomain string
|
||||||
method []byte
|
method string
|
||||||
methodStr string
|
|
||||||
path string
|
path string
|
||||||
middleware Middleware
|
middleware Middleware
|
||||||
formattedPath string
|
formattedPath string
|
||||||
|
@ -767,8 +730,8 @@ func (s bySubdomain) Less(i, j int) bool {
|
||||||
|
|
||||||
var _ Route = &route{}
|
var _ Route = &route{}
|
||||||
|
|
||||||
func newRoute(method []byte, subdomain string, path string, middleware Middleware) *route {
|
func newRoute(method string, subdomain string, path string, middleware Middleware) *route {
|
||||||
r := &route{name: path + subdomain, method: method, methodStr: string(method), subdomain: subdomain, path: path, middleware: middleware}
|
r := &route{name: path + subdomain, method: method, subdomain: subdomain, path: path, middleware: middleware}
|
||||||
r.formatPath()
|
r.formatPath()
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -816,10 +779,7 @@ func (r route) Subdomain() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r route) Method() string {
|
func (r route) Method() string {
|
||||||
if r.methodStr == "" {
|
return r.method
|
||||||
r.methodStr = string(r.method)
|
|
||||||
}
|
|
||||||
return r.methodStr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r route) Path() string {
|
func (r route) Path() string {
|
||||||
|
@ -865,7 +825,7 @@ const (
|
||||||
|
|
||||||
type (
|
type (
|
||||||
muxTree struct {
|
muxTree struct {
|
||||||
method []byte
|
method string
|
||||||
// subdomain is empty for default-hostname routes,
|
// subdomain is empty for default-hostname routes,
|
||||||
// ex: mysubdomain.
|
// ex: mysubdomain.
|
||||||
subdomain string
|
subdomain string
|
||||||
|
@ -886,9 +846,6 @@ type (
|
||||||
hostname string
|
hostname string
|
||||||
// if any of the trees contains not empty subdomain
|
// if any of the trees contains not empty subdomain
|
||||||
hosts bool
|
hosts bool
|
||||||
// if false then searching by unescaped path
|
|
||||||
// defaults to true
|
|
||||||
escapePath bool
|
|
||||||
// if false then the /something it's not the same as /something/
|
// if false then the /something it's not the same as /something/
|
||||||
// defaults to true
|
// defaults to true
|
||||||
correctPath bool
|
correctPath bool
|
||||||
|
@ -904,7 +861,6 @@ func newServeMux(logger *log.Logger) *serveMux {
|
||||||
lookups: make([]*route, 0),
|
lookups: make([]*route, 0),
|
||||||
errorHandlers: make(map[int]Handler, 0),
|
errorHandlers: make(map[int]Handler, 0),
|
||||||
hostname: DefaultServerHostname, // these are changing when the server is up
|
hostname: DefaultServerHostname, // these are changing when the server is up
|
||||||
escapePath: !DefaultDisablePathEscape,
|
|
||||||
correctPath: !DefaultDisablePathCorrection,
|
correctPath: !DefaultDisablePathCorrection,
|
||||||
fireMethodNotAllowed: false,
|
fireMethodNotAllowed: false,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
@ -917,10 +873,6 @@ func (mux *serveMux) setHostname(h string) {
|
||||||
mux.hostname = h
|
mux.hostname = h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mux *serveMux) setEscapePath(b bool) {
|
|
||||||
mux.escapePath = b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mux *serveMux) setCorrectPath(b bool) {
|
func (mux *serveMux) setCorrectPath(b bool) {
|
||||||
mux.correctPath = b
|
mux.correctPath = b
|
||||||
}
|
}
|
||||||
|
@ -948,7 +900,7 @@ func (mux *serveMux) fireError(statusCode int, ctx *Context) {
|
||||||
errHandler := mux.errorHandlers[statusCode]
|
errHandler := mux.errorHandlers[statusCode]
|
||||||
if errHandler == nil {
|
if errHandler == nil {
|
||||||
errHandler = HandlerFunc(func(ctx *Context) {
|
errHandler = HandlerFunc(func(ctx *Context) {
|
||||||
ctx.Response.Reset()
|
ctx.ResponseWriter.Reset()
|
||||||
ctx.SetStatusCode(statusCode)
|
ctx.SetStatusCode(statusCode)
|
||||||
ctx.SetBodyString(statusText[statusCode])
|
ctx.SetBodyString(statusText[statusCode])
|
||||||
})
|
})
|
||||||
|
@ -959,17 +911,17 @@ func (mux *serveMux) fireError(statusCode int, ctx *Context) {
|
||||||
errHandler.Serve(ctx)
|
errHandler.Serve(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mux *serveMux) getTree(method []byte, subdomain string) *muxTree {
|
func (mux *serveMux) getTree(method string, subdomain string) *muxTree {
|
||||||
for i := range mux.garden {
|
for i := range mux.garden {
|
||||||
t := mux.garden[i]
|
t := mux.garden[i]
|
||||||
if bytes.Equal(t.method, method) && t.subdomain == subdomain {
|
if t.method == method && t.subdomain == subdomain {
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mux *serveMux) register(method []byte, subdomain string, path string, middleware Middleware) *route {
|
func (mux *serveMux) register(method string, subdomain string, path string, middleware Middleware) *route {
|
||||||
mux.mu.Lock()
|
mux.mu.Lock()
|
||||||
defer mux.mu.Unlock()
|
defer mux.mu.Unlock()
|
||||||
|
|
||||||
|
@ -990,7 +942,7 @@ func (mux *serveMux) register(method []byte, subdomain string, path string, midd
|
||||||
|
|
||||||
// build collects all routes info and adds them to the registry in order to be served from the request handler
|
// build collects all routes info and adds them to the registry in order to be served from the request handler
|
||||||
// this happens once when server is setting the mux's handler.
|
// this happens once when server is setting the mux's handler.
|
||||||
func (mux *serveMux) build() (getRequestPath func(*fasthttp.RequestCtx) string, methodEqual func([]byte, []byte) bool) {
|
func (mux *serveMux) build() (methodEqual func(string, string) bool) {
|
||||||
|
|
||||||
sort.Sort(bySubdomain(mux.lookups))
|
sort.Sort(bySubdomain(mux.lookups))
|
||||||
|
|
||||||
|
@ -1014,26 +966,17 @@ func (mux *serveMux) build() (getRequestPath func(*fasthttp.RequestCtx) string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// optimize this once once, we could do that: context.RequestPath(mux.escapePath), but we lose some nanoseconds on if :)
|
methodEqual = func(reqMethod string, treeMethod string) bool {
|
||||||
getRequestPath = func(reqCtx *fasthttp.RequestCtx) string {
|
return reqMethod == treeMethod
|
||||||
return string(reqCtx.Path())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !mux.escapePath {
|
|
||||||
getRequestPath = func(reqCtx *fasthttp.RequestCtx) string { return string(reqCtx.RequestURI()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
methodEqual = func(reqMethod []byte, treeMethod []byte) bool {
|
|
||||||
return bytes.Equal(reqMethod, treeMethod)
|
|
||||||
}
|
}
|
||||||
// check for cors conflicts FIRST in order to put them in OPTIONS tree also
|
// check for cors conflicts FIRST in order to put them in OPTIONS tree also
|
||||||
for i := range mux.lookups {
|
for i := range mux.lookups {
|
||||||
r := mux.lookups[i]
|
r := mux.lookups[i]
|
||||||
if r.hasCors() {
|
if r.hasCors() {
|
||||||
// cors middleware is updated also, ref: https://github.com/kataras/iris/issues/461
|
// cors middleware is updated also, ref: https://github.com/kataras/iris/issues/461
|
||||||
methodEqual = func(reqMethod []byte, treeMethod []byte) bool {
|
methodEqual = func(reqMethod string, treeMethod string) bool {
|
||||||
// preflights
|
// preflights
|
||||||
return bytes.Equal(reqMethod, MethodOptionsBytes) || bytes.Equal(reqMethod, treeMethod)
|
return reqMethod == MethodOptions || reqMethod == treeMethod
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -1072,18 +1015,19 @@ func HTMLEscape(s string) string {
|
||||||
func (mux *serveMux) BuildHandler() HandlerFunc {
|
func (mux *serveMux) BuildHandler() HandlerFunc {
|
||||||
|
|
||||||
// initialize the router once
|
// initialize the router once
|
||||||
getRequestPath, methodEqual := mux.build()
|
methodEqual := mux.build()
|
||||||
|
|
||||||
return func(context *Context) {
|
return func(context *Context) {
|
||||||
routePath := getRequestPath(context.RequestCtx)
|
routePath := context.Path()
|
||||||
for i := range mux.garden {
|
for i := range mux.garden {
|
||||||
tree := mux.garden[i]
|
tree := mux.garden[i]
|
||||||
if !methodEqual(context.Method(), tree.method) {
|
if !methodEqual(context.Request.Method, tree.method) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if mux.hosts && tree.subdomain != "" {
|
if mux.hosts && tree.subdomain != "" {
|
||||||
// context.VirtualHost() is a slow method because it makes string.Replaces but user can understand that if subdomain then server will have some nano/or/milleseconds performance cost
|
// context.VirtualHost() is a slow method because it makes
|
||||||
|
// string.Replaces but user can understand that if subdomain then server will have some nano/or/milleseconds performance cost
|
||||||
requestHost := context.VirtualHostname()
|
requestHost := context.VirtualHostname()
|
||||||
if requestHost != mux.hostname {
|
if requestHost != mux.hostname {
|
||||||
//println(requestHost + " != " + mux.hostname)
|
//println(requestHost + " != " + mux.hostname)
|
||||||
|
@ -1111,8 +1055,7 @@ func (mux *serveMux) BuildHandler() HandlerFunc {
|
||||||
//ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent)
|
//ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent)
|
||||||
context.Do()
|
context.Do()
|
||||||
return
|
return
|
||||||
} else if mustRedirect && mux.correctPath && !bytes.Equal(context.Method(), MethodConnectBytes) {
|
} else if mustRedirect && mux.correctPath { // && context.Method() == MethodConnect {
|
||||||
|
|
||||||
reqPath := routePath
|
reqPath := routePath
|
||||||
pathLen := len(reqPath)
|
pathLen := len(reqPath)
|
||||||
|
|
||||||
|
@ -1124,23 +1067,22 @@ func (mux *serveMux) BuildHandler() HandlerFunc {
|
||||||
reqPath = reqPath + "/"
|
reqPath = reqPath + "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Request.URI().SetPath(reqPath)
|
urlToRedirect := reqPath
|
||||||
urlToRedirect := string(context.Request.RequestURI())
|
|
||||||
|
|
||||||
statisForRedirect := StatusMovedPermanently // StatusMovedPermanently, this document is obselte, clients caches this.
|
statusForRedirect := StatusMovedPermanently // StatusMovedPermanently, this document is obselte, clients caches this.
|
||||||
if bytes.Equal(tree.method, MethodPostBytes) ||
|
if tree.method == MethodPost ||
|
||||||
bytes.Equal(tree.method, MethodPutBytes) ||
|
tree.method == MethodPut ||
|
||||||
bytes.Equal(tree.method, MethodDeleteBytes) {
|
tree.method == MethodDelete {
|
||||||
statisForRedirect = StatusTemporaryRedirect // To maintain POST data
|
statusForRedirect = StatusTemporaryRedirect // To maintain POST data
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Redirect(urlToRedirect, statisForRedirect)
|
context.Redirect(urlToRedirect, statusForRedirect)
|
||||||
// RFC2616 recommends that a short note "SHOULD" be included in the
|
// RFC2616 recommends that a short note "SHOULD" be included in the
|
||||||
// response because older user agents may not understand 301/307.
|
// response because older user agents may not understand 301/307.
|
||||||
// Shouldn't send the response for POST or HEAD; that leaves GET.
|
// Shouldn't send the response for POST or HEAD; that leaves GET.
|
||||||
if bytes.Equal(tree.method, MethodGetBytes) {
|
if tree.method == MethodGet {
|
||||||
note := "<a href=\"" + HTMLEscape(urlToRedirect) + "\">Moved Permanently</a>.\n"
|
note := "<a href=\"" + HTMLEscape(urlToRedirect) + "\">Moved Permanently</a>.\n"
|
||||||
context.Write(note)
|
context.WriteString(note)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1408,13 +1350,14 @@ func ParseScheme(domain string) string {
|
||||||
return SchemeHTTP
|
return SchemeHTTP
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyHandler returns a new fasthttp handler which works as 'proxy', maybe doesn't suits you look its code before using that in production
|
// ProxyHandler returns a new net/http.Handler which works as 'proxy', maybe doesn't suits you look its code before using that in production
|
||||||
var ProxyHandler = func(proxyAddr string, redirectSchemeAndHost string) fasthttp.RequestHandler {
|
var ProxyHandler = func(redirectSchemeAndHost string) http.HandlerFunc {
|
||||||
return func(reqCtx *fasthttp.RequestCtx) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// override the handler and redirect all requests to this addr
|
// override the handler and redirect all requests to this addr
|
||||||
redirectTo := redirectSchemeAndHost
|
redirectTo := redirectSchemeAndHost
|
||||||
fakehost := string(reqCtx.Request.Host())
|
fakehost := r.URL.Host
|
||||||
path := string(reqCtx.Path())
|
path := r.URL.EscapedPath()
|
||||||
if strings.Count(fakehost, ".") >= 3 { // propably a subdomain, pure check but doesn't matters don't worry
|
if strings.Count(fakehost, ".") >= 3 { // propably a subdomain, pure check but doesn't matters don't worry
|
||||||
if sufIdx := strings.LastIndexByte(fakehost, '.'); sufIdx > 0 {
|
if sufIdx := strings.LastIndexByte(fakehost, '.'); sufIdx > 0 {
|
||||||
// check if the last part is a number instead of .com/.gr...
|
// check if the last part is a number instead of .com/.gr...
|
||||||
|
@ -1425,7 +1368,7 @@ var ProxyHandler = func(proxyAddr string, redirectSchemeAndHost string) fasthttp
|
||||||
realHost := strings.Replace(redirectSchemeAndHost, redirectScheme, "", 1)
|
realHost := strings.Replace(redirectSchemeAndHost, redirectScheme, "", 1)
|
||||||
redirectHost := strings.Replace(fakehost, fakehost, realHost, 1)
|
redirectHost := strings.Replace(fakehost, fakehost, realHost, 1)
|
||||||
redirectTo = redirectScheme + redirectHost + path
|
redirectTo = redirectScheme + redirectHost + path
|
||||||
reqCtx.Redirect(redirectTo, StatusMovedPermanently)
|
http.Redirect(w, r, redirectTo, StatusMovedPermanently)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1433,8 +1376,11 @@ var ProxyHandler = func(proxyAddr string, redirectSchemeAndHost string) fasthttp
|
||||||
if path != "/" {
|
if path != "/" {
|
||||||
redirectTo += path
|
redirectTo += path
|
||||||
}
|
}
|
||||||
|
if redirectTo == r.URL.String() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
reqCtx.Redirect(redirectTo, StatusMovedPermanently)
|
http.Redirect(w, r, redirectTo, StatusMovedPermanently)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1447,11 +1393,13 @@ func Proxy(proxyAddr string, redirectSchemeAndHost string) func() error {
|
||||||
proxyAddr = ParseHost(proxyAddr)
|
proxyAddr = ParseHost(proxyAddr)
|
||||||
|
|
||||||
// override the handler and redirect all requests to this addr
|
// override the handler and redirect all requests to this addr
|
||||||
h := ProxyHandler(proxyAddr, redirectSchemeAndHost)
|
h := ProxyHandler(redirectSchemeAndHost)
|
||||||
prx := New(OptionDisableBanner(true))
|
prx := New(OptionDisableBanner(true))
|
||||||
prx.Router = h
|
prx.Router = h
|
||||||
|
|
||||||
go prx.Listen(proxyAddr)
|
go prx.Listen(proxyAddr)
|
||||||
|
if ok := <-prx.Available; !ok {
|
||||||
return prx.Close
|
prx.Logger.Panic("Unexpected error: proxy server cannot start, please report this as bug!!")
|
||||||
|
}
|
||||||
|
return func() error { return prx.Close() }
|
||||||
}
|
}
|
||||||
|
|
222
http_test.go
222
http_test.go
|
@ -3,70 +3,69 @@ package iris_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gavv/httpexpect"
|
|
||||||
"github.com/kataras/go-errors"
|
|
||||||
"github.com/kataras/iris"
|
|
||||||
"github.com/kataras/iris/httptest"
|
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gavv/httpexpect"
|
||||||
|
"github.com/kataras/iris"
|
||||||
|
"github.com/kataras/iris/httptest"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
testTLSCert = `-----BEGIN CERTIFICATE-----
|
testTLSCert = `-----BEGIN CERTIFICATE-----
|
||||||
MIIDATCCAemgAwIBAgIJAPdE0ZyCKwVtMA0GCSqGSIb3DQEBBQUAMBcxFTATBgNV
|
MIIDAzCCAeugAwIBAgIJAP0pWSuIYyQCMA0GCSqGSIb3DQEBBQUAMBgxFjAUBgNV
|
||||||
BAMMDG15ZG9tYWluLmNvbTAeFw0xNjA5MjQwNjU3MDVaFw0yNjA5MjIwNjU3MDVa
|
BAMMDWxvY2FsaG9zdDozMzEwHhcNMTYxMjI1MDk1OTI3WhcNMjYxMjIzMDk1OTI3
|
||||||
MBcxFTATBgNVBAMMDG15ZG9tYWluLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP
|
WjAYMRYwFAYDVQQDDA1sb2NhbGhvc3Q6MzMxMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||||
ADCCAQoCggEBAM9YJOV1Bl+NwEq8ZAcVU2YONBw5zGkUFJUZkL77XT0i1V473JTf
|
AQ8AMIIBCgKCAQEA5vETjLa+8W856rWXO1xMF/CLss9vn5xZhPXKhgz+D7ogSAXm
|
||||||
GEpNZisDman+6n+pXarC2mR4T9PkCfmk072HaZ2LXwYe9XSgxnLJZJA1fJMzdMMC
|
mWP53eeBUGC2r26J++CYfVqwOmfJEu9kkGUVi8cGMY9dHeIFPfxD31MYX175jJQe
|
||||||
2XveINF+/eeoyW9+8ZjQPbZdHWcxi7RomXg1AOMAG2UWMjalK5xkTHcqDuOI2LEe
|
tu0WeUII7ciNsSUDyBMqsl7yi1IgN7iLONM++1+QfbbmNiEbghRV6icEH6M+bWlz
|
||||||
mezWHnFdBJNMTi3pNdbVr7BjexZTSGhx4LAIP2ufTUoVzk+Cvyr4IhS00zOiyyWv
|
3YSAMEdpK3mg2gsugfLKMwJkaBKEehUNMySRlIhyLITqt1exYGaggRd1zjqUpqpD
|
||||||
tuJaO20Q0Q5n34o9vDAACKAfNRLBE8qzdRwsjMumXTX3hJzvgFp/4Lr5Hr2I2fBd
|
sL2sRVHJ3qHGkSh8nVy8MvG8BXiFdYQJP3mCQDZzruCyMWj5/19KAyu7Cto3Bcvu
|
||||||
tbIWN9xIsu6IibBGFItiAfQSrKAR7IFVqDUCAwEAAaNQME4wHQYDVR0OBBYEFNvN
|
PgujnwRtU+itt8WhZUVtU1n7Ivf6lMJTBcc4OQIDAQABo1AwTjAdBgNVHQ4EFgQU
|
||||||
Yik2eBRDmDaqoMaLfvr75kGfMB8GA1UdIwQYMBaAFNvNYik2eBRDmDaqoMaLfvr7
|
MXrBvbILQmiwjUj19aecF2N+6IkwHwYDVR0jBBgwFoAUMXrBvbILQmiwjUj19aec
|
||||||
5kGfMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEAv3pKkmDTXIChB
|
F2N+6IkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA4zbFml1t9KXJ
|
||||||
nVrbYwNibin9HYOPf3JCjU48V672YPgbfJM0WfTvLXNVBOdTz3aIUrhfwv/go2Jz
|
OijAV8gALePR8v04DQwJP+jsRxXw5zzhc8Wqzdd2hjUd07mfRWAvmyywrmhCV6zq
|
||||||
yDcIFdLUdwllovhj1RwI96lbgCJ4AKoO/fvJ5Rxq+/vvLYB38PNl/fVEnOOeWzCQ
|
OHznR+aqIqHtm0vV8OpKxLoIQXavfBd6axEXt3859RDM4xJNwIlxs3+LWGPgINud
|
||||||
qHfjckShNV5GzJPhfpYn9Gj6+Zj3O0cJXhF9L/FlbVxFhwPjPRbFDNTHYzgiHy82
|
wjJqjyzSlhJpQpx4YZ5Da+VMiqAp8N1UeaZ5lBvmSDvoGh6HLODSqtPlWMrldRW9
|
||||||
zhhDhTQJVnNJXfokdlg9OjfFkySqpv9fvPi/dfk5j1KmKUiYP5SYUhZiKX1JScvx
|
AfsXVxenq81MIMeKW2fSOoPnWZ4Vjf1+dSlbJE/DD4zzcfbyfgY6Ep/RrUltJ3ag
|
||||||
JgesCwz1nUfVQ1TYE0dphU8mTCN4Y42i7bctx7iLcDRIFhtYstt0hhCKSxFmJSBG
|
FQbuNTQlgKabe21dSL9zJ2PengVKXl4Trl+4t/Kina9N9Jw535IRCSwinD6a/2Ca
|
||||||
y9RITRA=
|
m7DnVXFiVA==
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
`
|
`
|
||||||
|
|
||||||
testTLSKey = `-----BEGIN RSA PRIVATE KEY-----
|
testTLSKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||||
MIIEpQIBAAKCAQEAz1gk5XUGX43ASrxkBxVTZg40HDnMaRQUlRmQvvtdPSLVXjvc
|
MIIEpAIBAAKCAQEA5vETjLa+8W856rWXO1xMF/CLss9vn5xZhPXKhgz+D7ogSAXm
|
||||||
lN8YSk1mKwOZqf7qf6ldqsLaZHhP0+QJ+aTTvYdpnYtfBh71dKDGcslkkDV8kzN0
|
mWP53eeBUGC2r26J++CYfVqwOmfJEu9kkGUVi8cGMY9dHeIFPfxD31MYX175jJQe
|
||||||
wwLZe94g0X7956jJb37xmNA9tl0dZzGLtGiZeDUA4wAbZRYyNqUrnGRMdyoO44jY
|
tu0WeUII7ciNsSUDyBMqsl7yi1IgN7iLONM++1+QfbbmNiEbghRV6icEH6M+bWlz
|
||||||
sR6Z7NYecV0Ek0xOLek11tWvsGN7FlNIaHHgsAg/a59NShXOT4K/KvgiFLTTM6LL
|
3YSAMEdpK3mg2gsugfLKMwJkaBKEehUNMySRlIhyLITqt1exYGaggRd1zjqUpqpD
|
||||||
Ja+24lo7bRDRDmffij28MAAIoB81EsETyrN1HCyMy6ZdNfeEnO+AWn/guvkevYjZ
|
sL2sRVHJ3qHGkSh8nVy8MvG8BXiFdYQJP3mCQDZzruCyMWj5/19KAyu7Cto3Bcvu
|
||||||
8F21shY33Eiy7oiJsEYUi2IB9BKsoBHsgVWoNQIDAQABAoIBABRhi67qY+f8nQw7
|
PgujnwRtU+itt8WhZUVtU1n7Ivf6lMJTBcc4OQIDAQABAoIBAQCTLE0eHpPevtg0
|
||||||
nHF9zSbY+pJTtB4YFTXav3mmZ7HcvLB4neQcUdzr4sETp4UoQ5Cs60IfySvbD626
|
+FaRUMd5diVA5asoF3aBIjZXaU47bY0G+SO02x6wSMmDFK83a4Vpy/7B3Bp0jhF5
|
||||||
WqipZQ7aQq1zx7FoVaRTMW6TEUmDmG03v6BzpUEhwoQVQYwF8Vb+WW01+vr0CDHe
|
DLCUyKaLdmE/EjLwSUq37ty+JHFizd7QtNBCGSN6URfpmSabHpCjX3uVQqblHIhF
|
||||||
kub26S8BtsaZehfjqKfqcHD9Au8ri+Nwbu91iT4POVzBBBwYbtwXZwaYDR5PCNOI
|
mki3BQCdJ5CoXPemxUCHjDgYSZb6JVNIPJExjekc0+4A2MYWMXV6Wr86C7AY3659
|
||||||
ld+6qLapVIVKpvLHL+tA4A/n0n4l7p8TJo/qYesFRZ7J+USt4YGFDuf15nnDge/7
|
KmveZpC3gOkLA/g/IqDQL/QgTq7/3eloHaO+uPBihdF56do4eaOO0jgFYpl8V7ek
|
||||||
9Qjyqa9WmvRGytPdgtEzc8XwJu7xhcRthSmCppdY0ExHBwVceCEz8u3QbRYFqq3U
|
PZhHfhuPZV3oq15+8Vt77ngtjUWVI6qX0E3ilh+V5cof+03q0FzHPVe3zBUNXcm0
|
||||||
iLXUpfkCgYEA6JMlRtLIEAPkJZGBxJBSaeWVOeaAaMnLAdcu4OFjSuxr65HXsdhM
|
OGz19u/FAoGBAPSm4Aa4xs/ybyjQakMNix9rak66ehzGkmlfeK5yuQ/fHmTg8Ac+
|
||||||
aWHopCE44NjBROAg67qgc5vNBZvhnCwyTI8nb6k/CO5QZ4AG1d2Xe/9rPV5pdaBL
|
ahGs6A3lFWQiyU6hqm6Qp0iKuxuDh35DJGCWAw5OUS/7WLJtu8fNFch6iIG29rFs
|
||||||
gRgOJjlG0apZpPVM4I/0JU5prwS2Z71lFmEMikwmbmngYmOysqUBfbMCgYEA5Dpw
|
s+Uz2YLxJPebpBsKymZUp7NyDRgEElkiqsREmbYjLrc8uNKkDy+k14YnAoGBAPGn
|
||||||
qzn1Oq+fasSUjzUa/wvBTVMpVjnNrda7Hnlx6lssnQaPFqifFYL9Zr/+5rWdsE2O
|
ZlN0Mo5iNgQStulYEP5pI7WOOax9KOYVnBNguqgY9c7fXVXBxChoxt5ebQJWG45y
|
||||||
NNCsy68tacspAUa0LQinzpeSP4dyTVCvZG/xWPaYDKE6FDmzsi8xHBTTxMneeU6p
|
KPG0hB0bkA4YPu4bTRf5acIMpjFwcxNlmwdc4oCkT4xqAFs9B/AKYZgkf4IfKHqW
|
||||||
HUKJMUD3LcvBiCLunhT2xd1/+LKzVce6ms9R3ncCgYEAv9wjdDmOMSgEnblbg/xL
|
P9PD7TbUpkaxv25bPYwUSEB7lPa+hBtRyN9Wo6qfAoGAPBkeISiU1hJE0i7YW55h
|
||||||
AHEUmZ89bzSI9Au/8GP+tWAz5zF47o2w+355nGyLr3EgfuEmR1C97KEqkOX3SA5t
|
FZfKZoqSYq043B+ywo+1/Dsf+UH0VKM1ZSAnZPpoVc/hyaoW9tAb98r0iZ620wJl
|
||||||
sBqoPcUw6v0t9zP2b5dN0Ez0+rtX5GFH6Ecf5Qh7E5ukOCDkOpyGnAADzw3kK9Bi
|
VkCjgYklknbY5APmw/8SIcxP6iVq1kzQqDYjcXIRVa3rEyWEcLzM8VzL8KFXbIQC
|
||||||
BAQrhCstyQguwvvb/uOAR2ECgYEA3nYcZrqaz7ZqVL8C88hW5S4HIKEkFNlJI97A
|
lPIRHFfqKuMEt+HLRTXmJ7MCgYAHGvv4QjdmVl7uObqlG9DMGj1RjlAF0VxNf58q
|
||||||
DAdiw4ZVqUXAady5HFXPPL1+8FEtQLGIIPEazXuWb53I/WZ2r8LVFunlcylKgBRa
|
NrLmVG2N2qV86wigg4wtZ6te4TdINfUcPkmQLYpLz8yx5Z2bsdq5OPP+CidoD5nC
|
||||||
sjLvdMEBGqZ5H0fTYabgXrfqZ9JBmcrTyyKU6b6icTBAF7u9DbfvhpTObZN6fO2v
|
WqnSTIKGR2uhQycjmLqL5a7WHaJsEFTqHh2wego1k+5kCUzC/KmvM7MKmkl6ICp+
|
||||||
dcEJ0ycCgYEAxM8nGR+pa16kZmW1QV62EN0ifrU7SOJHCOGApU0jvTz8D4GO2j+H
|
3qZLUwKBgQCDOhKDwYo1hdiXoOOQqg/LZmpWOqjO3b4p99B9iJqhmXN0GKXIPSBh
|
||||||
MsoPSBrZ++UYgtGO/dK4aBV1JDdy8ZdyfE6UN+a6dQgdNdbOMT4XwWdS0zlZ/+F4
|
5nqqmGsG8asSQhchs7EPMh8B80KbrDTeidWskZuUoQV27Al1UEmL6Zcl83qXD6sf
|
||||||
PKvbgZnLEKHvjODJ65aQmcTVUoaa11J29iAAtA3a3TcMn6C2fYpRy1s=
|
k9X9TwWyZtp5IL1CAEd/Il9ZTXFzr3lNaN8LCFnU+EIsz1YgUW8LTg==
|
||||||
-----END RSA PRIVATE KEY-----
|
-----END RSA PRIVATE KEY-----
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseAddr(t *testing.T) {
|
func TestParseAddr(t *testing.T) {
|
||||||
|
@ -155,7 +154,7 @@ func getRandomNumber(min int, max int) int {
|
||||||
// works as
|
// works as
|
||||||
// defer listenTLS(iris.Default, hostTLS)()
|
// defer listenTLS(iris.Default, hostTLS)()
|
||||||
func listenTLS(api *iris.Framework, hostTLS string) func() {
|
func listenTLS(api *iris.Framework, hostTLS string) func() {
|
||||||
api.Close() // close any previous server
|
api.Close() // close any prev listener
|
||||||
api.Config.DisableBanner = true
|
api.Config.DisableBanner = true
|
||||||
// create the key and cert files on the fly, and delete them when this test finished
|
// create the key and cert files on the fly, and delete them when this test finished
|
||||||
certFile, ferr := ioutil.TempFile("", "cert")
|
certFile, ferr := ioutil.TempFile("", "cert")
|
||||||
|
@ -179,68 +178,77 @@ func listenTLS(api *iris.Framework, hostTLS string) func() {
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
certFile.Close()
|
certFile.Close()
|
||||||
time.Sleep(150 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
os.Remove(certFile.Name())
|
os.Remove(certFile.Name())
|
||||||
|
|
||||||
keyFile.Close()
|
keyFile.Close()
|
||||||
time.Sleep(150 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
os.Remove(keyFile.Name())
|
os.Remove(keyFile.Name())
|
||||||
|
|
||||||
api.Close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains the server test for multi running servers
|
// Contains the server test for multi running servers
|
||||||
func TestMultiRunningServers_v1_PROXY(t *testing.T) {
|
func TestMultiRunningServers_v1_PROXY(t *testing.T) {
|
||||||
iris.ResetDefault()
|
api := iris.New()
|
||||||
|
|
||||||
host := "localhost" // you have to add it to your hosts file( for windows, as 127.0.0.1 mydomain.com)
|
host := "localhost"
|
||||||
hostTLS := host + ":" + strconv.Itoa(getRandomNumber(8886, 8889))
|
hostTLS := host + ":" + strconv.Itoa(getRandomNumber(1919, 2221))
|
||||||
|
api.Get("/", func(ctx *iris.Context) {
|
||||||
iris.Get("/", func(ctx *iris.Context) {
|
ctx.Writef("Hello from %s", hostTLS)
|
||||||
ctx.Write("Hello from %s", hostTLS)
|
|
||||||
})
|
})
|
||||||
|
// println("running main on: " + hostTLS)
|
||||||
|
|
||||||
proxyHost := host + ":" + strconv.Itoa(getRandomNumber(3300, 3332))
|
defer listenTLS(api, hostTLS)()
|
||||||
closeProxy := iris.Proxy(proxyHost, "https://"+hostTLS)
|
|
||||||
defer closeProxy()
|
|
||||||
|
|
||||||
defer listenTLS(iris.Default, hostTLS)()
|
e := httptest.New(api, t, httptest.ExplicitURL(true))
|
||||||
|
e.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
|
||||||
|
|
||||||
e := httptest.New(iris.Default, t, httptest.ExplicitURL(true))
|
// proxy http to https
|
||||||
|
proxyHost := host + ":" + strconv.Itoa(getRandomNumber(3300, 3340))
|
||||||
|
// println("running proxy on: " + proxyHost)
|
||||||
|
|
||||||
e.Request("GET", "http://"+proxyHost).Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
|
iris.Proxy(proxyHost, "https://"+hostTLS)
|
||||||
e.Request("GET", "https://"+hostTLS).Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
|
|
||||||
|
|
||||||
|
// proxySrv := &http.Server{Addr: proxyHost, Handler: iris.ProxyHandler("https://" + hostTLS)}
|
||||||
|
// go proxySrv.ListenAndServe()
|
||||||
|
// time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
eproxy := httptest.NewInsecure("http://"+proxyHost, t, httptest.ExplicitURL(true))
|
||||||
|
eproxy.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains the server test for multi running servers
|
// Contains the server test for multi running servers
|
||||||
func TestMultiRunningServers_v2(t *testing.T) {
|
func TestMultiRunningServers_v2(t *testing.T) {
|
||||||
iris.ResetDefault()
|
api := iris.New()
|
||||||
|
|
||||||
domain := "localhost"
|
domain := "localhost"
|
||||||
hostTLS := domain + ":" + strconv.Itoa(getRandomNumber(3333, 4444))
|
hostTLS := domain + ":" + strconv.Itoa(getRandomNumber(2222, 2229))
|
||||||
srv1Host := domain + ":" + strconv.Itoa(getRandomNumber(4446, 5444))
|
srv1Host := domain + ":" + strconv.Itoa(getRandomNumber(4446, 5444))
|
||||||
srv2Host := domain + ":" + strconv.Itoa(getRandomNumber(7778, 8887))
|
srv2Host := domain + ":" + strconv.Itoa(getRandomNumber(7778, 8887))
|
||||||
|
|
||||||
iris.Get("/", func(ctx *iris.Context) {
|
api.Get("/", func(ctx *iris.Context) {
|
||||||
ctx.Write("Hello from %s", hostTLS)
|
ctx.Writef("Hello from %s", hostTLS)
|
||||||
})
|
})
|
||||||
|
|
||||||
// using the proxy handler
|
defer listenTLS(api, hostTLS)()
|
||||||
fsrv1 := &fasthttp.Server{Handler: iris.ProxyHandler(srv1Host, "https://"+hostTLS)}
|
|
||||||
go fsrv1.ListenAndServe(srv1Host)
|
|
||||||
// using the same iris' handler but not as proxy, just the same handler
|
// using the same iris' handler but not as proxy, just the same handler
|
||||||
fsrv2 := &fasthttp.Server{Handler: iris.Default.Router}
|
srv2 := &http.Server{Handler: api.Router, Addr: srv2Host}
|
||||||
go fsrv2.ListenAndServe(srv2Host)
|
go srv2.ListenAndServe()
|
||||||
|
|
||||||
defer listenTLS(iris.Default, hostTLS)()
|
// using the proxy handler
|
||||||
|
srv1 := &http.Server{Handler: iris.ProxyHandler("https://" + hostTLS), Addr: srv1Host}
|
||||||
|
go srv1.ListenAndServe()
|
||||||
|
time.Sleep(500 * time.Millisecond) // wait a little for the http servers
|
||||||
|
|
||||||
e := httptest.New(iris.Default, t, httptest.ExplicitURL(true))
|
e := httptest.New(api, t, httptest.ExplicitURL(true))
|
||||||
|
e.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
|
||||||
|
|
||||||
e.Request("GET", "http://"+srv1Host).Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
|
eproxy1 := httptest.NewInsecure("http://"+srv1Host, t, httptest.ExplicitURL(true))
|
||||||
e.Request("GET", "http://"+srv2Host).Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
|
eproxy1.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
|
||||||
e.Request("GET", "https://"+hostTLS).Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
|
|
||||||
|
eproxy2 := httptest.NewInsecure("http://"+srv2Host, t)
|
||||||
|
eproxy2.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,7 +368,7 @@ func TestMuxSimple(t *testing.T) {
|
||||||
func TestMuxSimpleParty(t *testing.T) {
|
func TestMuxSimpleParty(t *testing.T) {
|
||||||
iris.ResetDefault()
|
iris.ResetDefault()
|
||||||
|
|
||||||
h := func(c *iris.Context) { c.WriteString(c.HostString() + c.PathString()) }
|
h := func(c *iris.Context) { c.WriteString(c.Request.URL.Host + c.Request.RequestURI) }
|
||||||
|
|
||||||
if testEnableSubdomain {
|
if testEnableSubdomain {
|
||||||
subdomainParty := iris.Party(testSubdomain + ".")
|
subdomainParty := iris.Party(testSubdomain + ".")
|
||||||
|
@ -422,8 +430,9 @@ func TestMuxPathEscape(t *testing.T) {
|
||||||
iris.ResetDefault()
|
iris.ResetDefault()
|
||||||
|
|
||||||
iris.Get("/details/:name", func(ctx *iris.Context) {
|
iris.Get("/details/:name", func(ctx *iris.Context) {
|
||||||
name := ctx.Param("name")
|
name := ctx.ParamDecoded("name")
|
||||||
highlight := ctx.URLParam("highlight")
|
highlight := ctx.URLParam("highlight")
|
||||||
|
|
||||||
ctx.Text(iris.StatusOK, fmt.Sprintf("name=%s,highlight=%s", name, highlight))
|
ctx.Text(iris.StatusOK, fmt.Sprintf("name=%s,highlight=%s", name, highlight))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -438,9 +447,10 @@ func TestMuxDecodeURL(t *testing.T) {
|
||||||
iris.ResetDefault()
|
iris.ResetDefault()
|
||||||
|
|
||||||
iris.Get("/encoding/:url", func(ctx *iris.Context) {
|
iris.Get("/encoding/:url", func(ctx *iris.Context) {
|
||||||
url := iris.DecodeURL(ctx.Param("url"))
|
url := ctx.ParamDecoded("url")
|
||||||
|
|
||||||
ctx.SetStatusCode(iris.StatusOK)
|
ctx.SetStatusCode(iris.StatusOK)
|
||||||
ctx.Write(url)
|
ctx.WriteString(url)
|
||||||
})
|
})
|
||||||
|
|
||||||
e := httptest.New(iris.Default, t)
|
e := httptest.New(iris.Default, t)
|
||||||
|
@ -487,11 +497,11 @@ func TestMuxCustomErrors(t *testing.T) {
|
||||||
|
|
||||||
// register the custom errors
|
// register the custom errors
|
||||||
iris.OnError(iris.StatusNotFound, func(ctx *iris.Context) {
|
iris.OnError(iris.StatusNotFound, func(ctx *iris.Context) {
|
||||||
ctx.Write("%s", notFoundMessage)
|
ctx.Writef("%s", notFoundMessage)
|
||||||
})
|
})
|
||||||
|
|
||||||
iris.OnError(iris.StatusInternalServerError, func(ctx *iris.Context) {
|
iris.OnError(iris.StatusInternalServerError, func(ctx *iris.Context) {
|
||||||
ctx.Write("%s", internalServerMessage)
|
ctx.Writef("%s", internalServerMessage)
|
||||||
})
|
})
|
||||||
|
|
||||||
// create httpexpect instance that will call fasthtpp.RequestHandler directly
|
// create httpexpect instance that will call fasthtpp.RequestHandler directly
|
||||||
|
@ -511,27 +521,27 @@ type testUserAPI struct {
|
||||||
|
|
||||||
// GET /users
|
// GET /users
|
||||||
func (u testUserAPI) Get() {
|
func (u testUserAPI) Get() {
|
||||||
u.Write("Get Users\n")
|
u.WriteString("Get Users\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /users/:param1 which its value passed to the id argument
|
// GET /users/:param1 which its value passed to the id argument
|
||||||
func (u testUserAPI) GetBy(id string) { // id equals to u.Param("param1")
|
func (u testUserAPI) GetBy(id string) { // id equals to u.Param("param1")
|
||||||
u.Write("Get By %s\n", id)
|
u.Writef("Get By %s\n", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT /users
|
// PUT /users
|
||||||
func (u testUserAPI) Put() {
|
func (u testUserAPI) Put() {
|
||||||
u.Write("Put, name: %s\n", u.FormValue("name"))
|
u.Writef("Put, name: %s\n", u.FormValue("name"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /users/:param1
|
// POST /users/:param1
|
||||||
func (u testUserAPI) PostBy(id string) {
|
func (u testUserAPI) PostBy(id string) {
|
||||||
u.Write("Post By %s, name: %s\n", id, u.FormValue("name"))
|
u.Writef("Post By %s, name: %s\n", id, u.FormValue("name"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE /users/:param1
|
// DELETE /users/:param1
|
||||||
func (u testUserAPI) DeleteBy(id string) {
|
func (u testUserAPI) DeleteBy(id string) {
|
||||||
u.Write("Delete By %s\n", id)
|
u.Writef("Delete By %s\n", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMuxAPI(t *testing.T) {
|
func TestMuxAPI(t *testing.T) {
|
||||||
|
@ -544,7 +554,7 @@ func TestMuxAPI(t *testing.T) {
|
||||||
ctx.Next()
|
ctx.Next()
|
||||||
}, func(ctx *iris.Context) {
|
}, func(ctx *iris.Context) {
|
||||||
if ctx.Get("user") == "username" {
|
if ctx.Get("user") == "username" {
|
||||||
ctx.Write(middlewareResponseText)
|
ctx.WriteString(middlewareResponseText)
|
||||||
ctx.Next()
|
ctx.Next()
|
||||||
} else {
|
} else {
|
||||||
ctx.SetStatusCode(iris.StatusUnauthorized)
|
ctx.SetStatusCode(iris.StatusUnauthorized)
|
||||||
|
@ -630,11 +640,11 @@ func TestMuxFireMethodNotAllowed(t *testing.T) {
|
||||||
iris.ResetDefault()
|
iris.ResetDefault()
|
||||||
iris.Default.Config.FireMethodNotAllowed = true
|
iris.Default.Config.FireMethodNotAllowed = true
|
||||||
h := func(ctx *iris.Context) {
|
h := func(ctx *iris.Context) {
|
||||||
ctx.Write("%s", ctx.MethodString())
|
ctx.WriteString(ctx.Method())
|
||||||
}
|
}
|
||||||
|
|
||||||
iris.Default.OnError(iris.StatusMethodNotAllowed, func(ctx *iris.Context) {
|
iris.Default.OnError(iris.StatusMethodNotAllowed, func(ctx *iris.Context) {
|
||||||
ctx.Write("Hello from my custom 405 page")
|
ctx.WriteString("Hello from my custom 405 page")
|
||||||
})
|
})
|
||||||
|
|
||||||
iris.Get("/mypath", h)
|
iris.Get("/mypath", h)
|
||||||
|
@ -650,6 +660,7 @@ func TestMuxFireMethodNotAllowed(t *testing.T) {
|
||||||
iris.Close()
|
iris.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
var (
|
var (
|
||||||
cacheDuration = 2 * time.Second
|
cacheDuration = 2 * time.Second
|
||||||
errCacheTestFailed = errors.New("Expected the main handler to be executed %d times instead of %d.")
|
errCacheTestFailed = errors.New("Expected the main handler to be executed %d times instead of %d.")
|
||||||
|
@ -680,7 +691,7 @@ func runCacheTest(e *httpexpect.Expect, path string, counterPtr *uint32, expecte
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Inside github.com/geekypanda/httpcache are enough, no need to add 10+ seconds of testing here.
|
Inside github.com/geekypanda/httpcache are enough, no need to add 10+ seconds of testing here.
|
||||||
func TestCache(t *testing.T) {
|
func TestCache(t *testing.T) {
|
||||||
|
|
||||||
|
@ -714,15 +725,18 @@ func TestCache(t *testing.T) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func TestRedirectHTTPS(t *testing.T) {
|
func TestRedirectHTTPS(t *testing.T) {
|
||||||
iris.ResetDefault()
|
|
||||||
host := "localhost:" + strconv.Itoa(getRandomNumber(4545, 5555))
|
|
||||||
expectedBody := "Redirected to https://" + host + "/redirected"
|
|
||||||
|
|
||||||
iris.Get("/redirect", func(ctx *iris.Context) { ctx.Redirect("/redirected") })
|
api := iris.New(iris.OptionIsDevelopment(true))
|
||||||
iris.Get("/redirected", func(ctx *iris.Context) { ctx.Text(iris.StatusOK, "Redirected to "+ctx.URI().String()) })
|
|
||||||
|
|
||||||
defer listenTLS(iris.Default, host)()
|
host := "localhost:" + strconv.Itoa(getRandomNumber(1717, 9281))
|
||||||
|
|
||||||
|
expectedBody := "Redirected to /redirected"
|
||||||
|
|
||||||
|
api.Get("/redirect", func(ctx *iris.Context) { ctx.Redirect("/redirected") })
|
||||||
|
api.Get("/redirected", func(ctx *iris.Context) { ctx.Text(iris.StatusOK, "Redirected to "+ctx.Path()) })
|
||||||
|
defer listenTLS(api, host)()
|
||||||
|
|
||||||
|
e := httptest.New(api, t)
|
||||||
|
e.GET("/redirect").Expect().Status(iris.StatusOK).Body().Equal(expectedBody)
|
||||||
|
|
||||||
e := httptest.New(iris.Default, t, httptest.ExplicitURL(true))
|
|
||||||
e.Request("GET", "https://"+host+"/redirect").Expect().Status(iris.StatusOK).Body().Equal(expectedBody)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package httptest
|
package httptest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gavv/httpexpect"
|
"crypto/tls"
|
||||||
"github.com/kataras/iris"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gavv/httpexpect"
|
||||||
|
"github.com/kataras/iris"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -94,7 +96,35 @@ func New(api *iris.Framework, t *testing.T, setters ...OptionSetter) *httpexpect
|
||||||
testConfiguration := httpexpect.Config{
|
testConfiguration := httpexpect.Config{
|
||||||
BaseURL: baseURL,
|
BaseURL: baseURL,
|
||||||
Client: &http.Client{
|
Client: &http.Client{
|
||||||
Transport: httpexpect.NewFastBinder(api.Router),
|
Transport: httpexpect.NewBinder(api.Router),
|
||||||
|
Jar: httpexpect.NewJar(),
|
||||||
|
},
|
||||||
|
Reporter: httpexpect.NewAssertReporter(t),
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.Debug {
|
||||||
|
testConfiguration.Printers = []httpexpect.Printer{
|
||||||
|
httpexpect.NewDebugPrinter(t, true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpexpect.WithConfig(testConfiguration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInsecure same as New but receives a single host instead of the whole framework
|
||||||
|
func NewInsecure(baseURL string, t *testing.T, setters ...OptionSetter) *httpexpect.Expect {
|
||||||
|
conf := DefaultConfiguration()
|
||||||
|
for _, setter := range setters {
|
||||||
|
setter.Set(conf)
|
||||||
|
}
|
||||||
|
transport := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
testConfiguration := httpexpect.Config{
|
||||||
|
BaseURL: baseURL,
|
||||||
|
Client: &http.Client{
|
||||||
|
Transport: transport,
|
||||||
Jar: httpexpect.NewJar(),
|
Jar: httpexpect.NewJar(),
|
||||||
},
|
},
|
||||||
Reporter: httpexpect.NewAssertReporter(t),
|
Reporter: httpexpect.NewAssertReporter(t),
|
||||||
|
|
662
iris.go
662
iris.go
|
@ -13,6 +13,7 @@ func main() {
|
||||||
c.JSON(iris.StatusOK, iris.Map{
|
c.JSON(iris.StatusOK, iris.Map{
|
||||||
"Name": "Iris",
|
"Name": "Iris",
|
||||||
"Released": "13 March 2016",
|
"Released": "13 March 2016",
|
||||||
|
"Stars": "5883",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
iris.ListenLETSENCRYPT("mydomain.com")
|
iris.ListenLETSENCRYPT("mydomain.com")
|
||||||
|
@ -27,9 +28,10 @@ import "github.com/kataras/iris"
|
||||||
func main() {
|
func main() {
|
||||||
s1 := iris.New()
|
s1 := iris.New()
|
||||||
s1.Get("/hi_json", func(c *iris.Context) {
|
s1.Get("/hi_json", func(c *iris.Context) {
|
||||||
c.JSON(200, iris.Map{
|
c.JSON(iris.StatusOK, iris.Map{
|
||||||
"Name": "Iris",
|
"Name": "Iris",
|
||||||
"Released": "13 March 2016",
|
"Released": "13 March 2016",
|
||||||
|
"Stars": "5883",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -47,7 +49,7 @@ func main() {
|
||||||
For middleware, template engines, response engines, sessions, websockets, mails, subdomains,
|
For middleware, template engines, response engines, sessions, websockets, mails, subdomains,
|
||||||
dynamic subdomains, routes, party of subdomains & routes, ssh and much more
|
dynamic subdomains, routes, party of subdomains & routes, ssh and much more
|
||||||
|
|
||||||
visit https://www.gitbook.com/book/kataras/iris/details
|
visit https://docs.iris-go.com
|
||||||
*/
|
*/
|
||||||
package iris // import "github.com/kataras/iris"
|
package iris // import "github.com/kataras/iris"
|
||||||
|
|
||||||
|
@ -56,6 +58,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
@ -72,15 +75,13 @@ import (
|
||||||
"github.com/kataras/go-sessions"
|
"github.com/kataras/go-sessions"
|
||||||
"github.com/kataras/go-template"
|
"github.com/kataras/go-template"
|
||||||
"github.com/kataras/go-template/html"
|
"github.com/kataras/go-template/html"
|
||||||
"github.com/kataras/iris/utils"
|
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// IsLongTermSupport flag is true when the below version number is a long-term-support version
|
// IsLongTermSupport flag is true when the below version number is a long-term-support version
|
||||||
IsLongTermSupport = false
|
IsLongTermSupport = false
|
||||||
// Version is the current version number of the Iris web framework
|
// Version is the current version number of the Iris web framework
|
||||||
Version = "5.1.3"
|
Version = "6.0.0"
|
||||||
|
|
||||||
banner = ` _____ _
|
banner = ` _____ _
|
||||||
|_ _| (_)
|
|_ _| (_)
|
||||||
|
@ -96,7 +97,7 @@ var (
|
||||||
Config *Configuration
|
Config *Configuration
|
||||||
Logger *log.Logger // if you want colors in your console then you should use this https://github.com/iris-contrib/logger instead.
|
Logger *log.Logger // if you want colors in your console then you should use this https://github.com/iris-contrib/logger instead.
|
||||||
Plugins PluginContainer
|
Plugins PluginContainer
|
||||||
Router fasthttp.RequestHandler
|
Router http.Handler
|
||||||
Websocket *WebsocketServer
|
Websocket *WebsocketServer
|
||||||
// Available is a channel type of bool, fired to true when the server is opened and all plugins ran
|
// Available is a channel type of bool, fired to true when the server is opened and all plugins ran
|
||||||
// never fires false, if the .Close called then the channel is re-allocating.
|
// never fires false, if the .Close called then the channel is re-allocating.
|
||||||
|
@ -149,7 +150,7 @@ type (
|
||||||
ListenUNIX(string, os.FileMode)
|
ListenUNIX(string, os.FileMode)
|
||||||
Close() error
|
Close() error
|
||||||
Reserve() error
|
Reserve() error
|
||||||
AcquireCtx(*fasthttp.RequestCtx) *Context
|
AcquireCtx(http.ResponseWriter, *http.Request) *Context
|
||||||
ReleaseCtx(*Context)
|
ReleaseCtx(*Context)
|
||||||
CheckForUpdates(bool)
|
CheckForUpdates(bool)
|
||||||
UseSessionDB(sessions.Database)
|
UseSessionDB(sessions.Database)
|
||||||
|
@ -168,35 +169,81 @@ type (
|
||||||
Cache(HandlerFunc, time.Duration) HandlerFunc
|
Cache(HandlerFunc, time.Duration) HandlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Framework is our God |\| Google.Search('Greek mythology Iris')
|
// MuxAPI the visible api for the serveMux
|
||||||
//
|
MuxAPI interface {
|
||||||
// Implements the FrameworkAPI
|
Party(string, ...HandlerFunc) MuxAPI
|
||||||
Framework struct {
|
// middleware serial, appending
|
||||||
*muxAPI
|
Use(...Handler) MuxAPI
|
||||||
// HTTP Server runtime fields is the iris' defined main server, developer can use unlimited number of servers
|
UseFunc(...HandlerFunc) MuxAPI
|
||||||
// note: they're available after .Build, and .Serve/Listen/ListenTLS/ListenLETSENCRYPT/ListenUNIX
|
Done(...Handler) MuxAPI
|
||||||
ln net.Listener
|
DoneFunc(...HandlerFunc) MuxAPI
|
||||||
fsrv *fasthttp.Server
|
|
||||||
Available chan bool
|
|
||||||
//
|
|
||||||
// Router field which can change the default iris' mux behavior
|
|
||||||
// if you want to get benefit with iris' context make use of:
|
|
||||||
// ctx:= iris.AcquireCtx(*fasthttp.RequestCtx) to get the context at the beginning of your handler
|
|
||||||
// iris.ReleaseCtx(ctx) to release/put the context to the pool, at the very end of your custom handler.
|
|
||||||
Router fasthttp.RequestHandler
|
|
||||||
|
|
||||||
contextPool sync.Pool
|
// main handlers
|
||||||
once sync.Once
|
Handle(string, string, ...Handler) RouteNameFunc
|
||||||
Config *Configuration
|
HandleFunc(string, string, ...HandlerFunc) RouteNameFunc
|
||||||
sessions sessions.Sessions
|
API(string, HandlerAPI, ...HandlerFunc)
|
||||||
serializers serializer.Serializers
|
|
||||||
templates *templateEngines
|
// http methods
|
||||||
Logger *log.Logger
|
Get(string, ...HandlerFunc) RouteNameFunc
|
||||||
Plugins PluginContainer
|
Post(string, ...HandlerFunc) RouteNameFunc
|
||||||
Websocket *WebsocketServer
|
Put(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Delete(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Connect(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Head(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Options(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Patch(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Trace(string, ...HandlerFunc) RouteNameFunc
|
||||||
|
Any(string, ...HandlerFunc)
|
||||||
|
|
||||||
|
// static content
|
||||||
|
StaticServe(string, ...string) RouteNameFunc
|
||||||
|
StaticContent(string, string, []byte) RouteNameFunc
|
||||||
|
StaticEmbedded(string, string, func(string) ([]byte, error), func() []string) RouteNameFunc
|
||||||
|
Favicon(string, ...string) RouteNameFunc
|
||||||
|
// static file system
|
||||||
|
StaticHandler(string, string, bool, bool) HandlerFunc
|
||||||
|
StaticWeb(string, string) RouteNameFunc
|
||||||
|
|
||||||
|
// party layout for template engines
|
||||||
|
Layout(string) MuxAPI
|
||||||
|
|
||||||
|
// errors
|
||||||
|
OnError(int, HandlerFunc)
|
||||||
|
EmitError(int, *Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RouteNameFunc the func returns from the MuxAPi's methods, optionally sets the name of the Route (*route)
|
||||||
|
RouteNameFunc func(string)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Framework is our God |\| Google.Search('Greek mythology Iris')
|
||||||
|
//
|
||||||
|
// Implements the FrameworkAPI
|
||||||
|
type Framework struct {
|
||||||
|
*muxAPI
|
||||||
|
// HTTP Server runtime fields is the iris' defined main server, developer can use unlimited number of servers
|
||||||
|
// note: they're available after .Build, and .Serve/Listen/ListenTLS/ListenLETSENCRYPT/ListenUNIX
|
||||||
|
ln net.Listener
|
||||||
|
srv *http.Server
|
||||||
|
Available chan bool
|
||||||
|
//
|
||||||
|
// Router field which can change the default iris' mux behavior
|
||||||
|
// if you want to get benefit with iris' context make use of:
|
||||||
|
// ctx:= iris.AcquireCtx(http.ResponseWriter, *http.Request) to get the context at the beginning of your handler
|
||||||
|
// iris.ReleaseCtx(ctx) to release/put the context to the pool, at the very end of your custom handler.
|
||||||
|
Router http.Handler
|
||||||
|
|
||||||
|
contextPool sync.Pool
|
||||||
|
once sync.Once
|
||||||
|
Config *Configuration
|
||||||
|
sessions sessions.Sessions
|
||||||
|
serializers serializer.Serializers
|
||||||
|
templates *templateEngines
|
||||||
|
Logger *log.Logger
|
||||||
|
Plugins PluginContainer
|
||||||
|
Websocket *WebsocketServer
|
||||||
|
}
|
||||||
|
|
||||||
var _ FrameworkAPI = &Framework{}
|
var _ FrameworkAPI = &Framework{}
|
||||||
|
|
||||||
// New creates and returns a new Iris instance.
|
// New creates and returns a new Iris instance.
|
||||||
|
@ -230,7 +277,9 @@ func New(setters ...OptionSetter) *Framework {
|
||||||
|
|
||||||
// websocket & sessions
|
// websocket & sessions
|
||||||
{
|
{
|
||||||
s.Websocket = NewWebsocketServer() // in order to be able to call $instance.Websocket.OnConnection
|
// in order to be able to call $instance.Websocket.OnConnection
|
||||||
|
// the whole ws configuration and websocket server is really initialized only on first OnConnection
|
||||||
|
s.Websocket = NewWebsocketServer(s)
|
||||||
|
|
||||||
// set the sessions, look .initialize for its GC
|
// set the sessions, look .initialize for its GC
|
||||||
s.sessions = sessions.New(sessions.DisableAutoGC(true))
|
s.sessions = sessions.New(sessions.DisableAutoGC(true))
|
||||||
|
@ -283,13 +332,14 @@ func Must(err error) {
|
||||||
// Must panics on error, it panics on registed iris' logger
|
// Must panics on error, it panics on registed iris' logger
|
||||||
func (s *Framework) Must(err error) {
|
func (s *Framework) Must(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Logger.Panic(err.Error())
|
// s.Logger.Panicf("%s. Trace:\n%s", err, debug.Stack())
|
||||||
|
s.Logger.Panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build builds the whole framework's parts together
|
// Build builds the whole framework's parts together
|
||||||
// DO NOT CALL IT MANUALLY IF YOU ARE NOT:
|
// DO NOT CALL IT MANUALLY IF YOU ARE NOT:
|
||||||
// SERVE IRIS BEHIND AN EXTERNAL CUSTOM fasthttp.Server, CAN BE CALLED ONCE PER IRIS INSTANCE FOR YOUR SAFETY
|
// SERVE IRIS BEHIND AN EXTERNAL CUSTOM net/http.Server, CAN BE CALLED ONCE PER IRIS INSTANCE FOR YOUR SAFETY
|
||||||
func Build() {
|
func Build() {
|
||||||
Default.Build()
|
Default.Build()
|
||||||
}
|
}
|
||||||
|
@ -345,14 +395,8 @@ func (s *Framework) Build() {
|
||||||
s.sessions.Set(s.Config.Sessions, sessions.DisableAutoGC(false))
|
s.sessions.Set(s.Config.Sessions, sessions.DisableAutoGC(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Config.Websocket.Endpoint != "" {
|
|
||||||
// register the websocket server and listen to websocket connections when/if $instance.Websocket.OnConnection called by the dev
|
|
||||||
s.Websocket.RegisterTo(s, s.Config.Websocket)
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare the mux runtime fields again, for any case
|
// prepare the mux runtime fields again, for any case
|
||||||
s.mux.setCorrectPath(!s.Config.DisablePathCorrection)
|
s.mux.setCorrectPath(!s.Config.DisablePathCorrection)
|
||||||
s.mux.setEscapePath(!s.Config.DisablePathEscape)
|
|
||||||
s.mux.setFireMethodNotAllowed(s.Config.FireMethodNotAllowed)
|
s.mux.setFireMethodNotAllowed(s.Config.FireMethodNotAllowed)
|
||||||
|
|
||||||
// prepare the server's handler, we do that check because iris supports
|
// prepare the server's handler, we do that check because iris supports
|
||||||
|
@ -360,12 +404,12 @@ func (s *Framework) Build() {
|
||||||
if s.Router == nil {
|
if s.Router == nil {
|
||||||
// build and get the default mux' handler(*Context)
|
// build and get the default mux' handler(*Context)
|
||||||
serve := s.mux.BuildHandler()
|
serve := s.mux.BuildHandler()
|
||||||
// build the fasthttp handler to bind it to the servers
|
// build the net/http.Handler to bind it to the servers
|
||||||
defaultHandler := func(reqCtx *fasthttp.RequestCtx) {
|
defaultHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := s.AcquireCtx(reqCtx)
|
ctx := s.AcquireCtx(w, r)
|
||||||
serve(ctx)
|
serve(ctx)
|
||||||
s.ReleaseCtx(ctx)
|
s.ReleaseCtx(ctx)
|
||||||
}
|
})
|
||||||
|
|
||||||
s.Router = defaultHandler
|
s.Router = defaultHandler
|
||||||
}
|
}
|
||||||
|
@ -375,19 +419,20 @@ func (s *Framework) Build() {
|
||||||
|
|
||||||
if s.ln != nil { // user called Listen functions or Serve,
|
if s.ln != nil { // user called Listen functions or Serve,
|
||||||
// create the main server
|
// create the main server
|
||||||
srvName := "iris"
|
s.srv = &http.Server{
|
||||||
if len(DefaultServerName) > 0 {
|
ReadTimeout: s.Config.ReadTimeout,
|
||||||
srvName += "_" + DefaultServerName
|
WriteTimeout: s.Config.WriteTimeout,
|
||||||
|
MaxHeaderBytes: s.Config.MaxHeaderBytes,
|
||||||
|
TLSNextProto: s.Config.TLSNextProto,
|
||||||
|
ConnState: s.Config.ConnState,
|
||||||
|
Handler: s.Router,
|
||||||
|
Addr: s.Config.VHost,
|
||||||
}
|
}
|
||||||
s.fsrv = &fasthttp.Server{Name: srvName,
|
if s.Config.TLSNextProto != nil {
|
||||||
MaxRequestBodySize: s.Config.MaxRequestBodySize,
|
s.srv.TLSNextProto = s.Config.TLSNextProto
|
||||||
ReadBufferSize: s.Config.ReadBufferSize,
|
}
|
||||||
WriteBufferSize: s.Config.WriteBufferSize,
|
if s.Config.ConnState != nil {
|
||||||
ReadTimeout: s.Config.ReadTimeout,
|
s.srv.ConnState = s.Config.ConnState
|
||||||
WriteTimeout: s.Config.WriteTimeout,
|
|
||||||
MaxConnsPerIP: s.Config.MaxConnsPerIP,
|
|
||||||
MaxRequestsPerConn: s.Config.MaxRequestsPerConn,
|
|
||||||
Handler: s.Router,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,9 +480,8 @@ func (s *Framework) Serve(ln net.Listener) error {
|
||||||
s.Logger.Panic(err)
|
s.Logger.Panic(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// start the server in goroutine, .Available will block instead
|
// start the server in goroutine, .Available will block instead
|
||||||
go func() { s.Must(s.fsrv.Serve(ln)) }()
|
go func() { s.Must(s.srv.Serve(ln)) }()
|
||||||
|
|
||||||
if !s.Config.DisableBanner {
|
if !s.Config.DisableBanner {
|
||||||
bannerMessage := fmt.Sprintf("%s: Running at %s", time.Now().Format(s.Config.TimeFormat), s.Config.VHost)
|
bannerMessage := fmt.Sprintf("%s: Running at %s", time.Now().Format(s.Config.TimeFormat), s.Config.VHost)
|
||||||
|
@ -524,7 +568,6 @@ func (s *Framework) ListenTLS(addr string, certFile, keyFile string) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Logger.Panic(err)
|
s.Logger.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Must(s.Serve(ln))
|
s.Must(s.Serve(ln))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -606,32 +649,6 @@ func (s *Framework) IsRunning() bool {
|
||||||
return s != nil && s.ln != nil && s.ln.Addr() != nil && s.ln.Addr().String() != ""
|
return s != nil && s.ln != nil && s.ln.Addr() != nil && s.ln.Addr().String() != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableKeepalive whether to disable keep-alive connections.
|
|
||||||
//
|
|
||||||
// The server will close all the incoming connections after sending
|
|
||||||
// the first response to client if this option is set to true.
|
|
||||||
//
|
|
||||||
// By default keep-alive connections are enabled
|
|
||||||
//
|
|
||||||
// Note: Used on packages like graceful, after the server runs.
|
|
||||||
func DisableKeepalive(val bool) {
|
|
||||||
Default.DisableKeepalive(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableKeepalive whether to disable keep-alive connections.
|
|
||||||
//
|
|
||||||
// The server will close all the incoming connections after sending
|
|
||||||
// the first response to client if this option is set to true.
|
|
||||||
//
|
|
||||||
// By default keep-alive connections are enabled
|
|
||||||
//
|
|
||||||
// Note: Used on packages like graceful, after the server runs.
|
|
||||||
func (s *Framework) DisableKeepalive(val bool) {
|
|
||||||
if s.fsrv != nil {
|
|
||||||
s.fsrv.DisableKeepalive = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close terminates all the registered servers and returns an error if any
|
// Close terminates all the registered servers and returns an error if any
|
||||||
// if you want to panic on this error use the iris.Must(iris.Close())
|
// if you want to panic on this error use the iris.Must(iris.Close())
|
||||||
func Close() error {
|
func Close() error {
|
||||||
|
@ -663,15 +680,16 @@ func (s *Framework) Reserve() error {
|
||||||
|
|
||||||
// AcquireCtx gets an Iris' Context from pool
|
// AcquireCtx gets an Iris' Context from pool
|
||||||
// see .ReleaseCtx & .Serve
|
// see .ReleaseCtx & .Serve
|
||||||
func AcquireCtx(reqCtx *fasthttp.RequestCtx) *Context {
|
func AcquireCtx(w http.ResponseWriter, r *http.Request) *Context {
|
||||||
return Default.AcquireCtx(reqCtx)
|
return Default.AcquireCtx(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcquireCtx gets an Iris' Context from pool
|
// AcquireCtx gets an Iris' Context from pool
|
||||||
// see .ReleaseCtx & .Serve
|
// see .ReleaseCtx & .Serve
|
||||||
func (s *Framework) AcquireCtx(reqCtx *fasthttp.RequestCtx) *Context {
|
func (s *Framework) AcquireCtx(w http.ResponseWriter, r *http.Request) *Context {
|
||||||
ctx := s.contextPool.Get().(*Context) // Changed to use the pool's New 09/07/2016, ~ -4k nanoseconds(9 bench tests) per requests (better performance)
|
ctx := s.contextPool.Get().(*Context) // Changed to use the pool's New 09/07/2016, ~ -4k nanoseconds(9 bench tests) per requests (better performance)
|
||||||
ctx.RequestCtx = reqCtx
|
ctx.ResponseWriter = acquireResponseWriter(w)
|
||||||
|
ctx.Request = r
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -684,8 +702,15 @@ func ReleaseCtx(ctx *Context) {
|
||||||
// ReleaseCtx puts the Iris' Context back to the pool in order to be re-used
|
// ReleaseCtx puts the Iris' Context back to the pool in order to be re-used
|
||||||
// see .AcquireCtx & .Serve
|
// see .AcquireCtx & .Serve
|
||||||
func (s *Framework) ReleaseCtx(ctx *Context) {
|
func (s *Framework) ReleaseCtx(ctx *Context) {
|
||||||
|
// flush the body when all finished
|
||||||
|
ctx.ResponseWriter.flushResponse()
|
||||||
|
|
||||||
ctx.Middleware = nil
|
ctx.Middleware = nil
|
||||||
ctx.session = nil
|
ctx.session = nil
|
||||||
|
ctx.Request = nil
|
||||||
|
releaseResponseWriter(ctx.ResponseWriter)
|
||||||
|
ctx.values.Reset()
|
||||||
|
|
||||||
s.contextPool.Put(ctx)
|
s.contextPool.Put(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -978,37 +1003,35 @@ func (s *Framework) Path(routeName string, args ...interface{}) string {
|
||||||
return fmt.Sprintf(r.formattedPath, arguments...)
|
return fmt.Sprintf(r.formattedPath, arguments...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeURL returns the uri parameter as url (string)
|
// DecodeQuery returns the uri parameter as url (string)
|
||||||
// useful when you want to pass something to a database and be valid to retrieve it via context.Param
|
// 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.
|
// use it only for special cases, when the default behavior doesn't suits you.
|
||||||
//
|
//
|
||||||
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
|
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
|
||||||
// it uses just the url.QueryUnescape
|
// it uses just the url.QueryUnescape
|
||||||
func DecodeURL(uri string) string {
|
func DecodeQuery(path string) string {
|
||||||
if uri == "" {
|
if path == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
encodedPath, _ := url.QueryUnescape(uri)
|
encodedPath, err := url.QueryUnescape(path)
|
||||||
|
if err != nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
return encodedPath
|
return encodedPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeFasthttpURL returns the path decoded as url
|
// DecodeURL returns the decoded uri
|
||||||
// useful when you want to pass something to a database and be valid to retrieve it via context.Param
|
// 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.
|
// use it only for special cases, when the default behavior doesn't suits you.
|
||||||
//
|
//
|
||||||
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
|
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
|
||||||
/* Credits to Manish Singh @kryptodev for URLDecode by post issue share code */
|
// it uses just the url.Parse
|
||||||
// simple things, if DecodeURL doesn't gives you the results you waited, use this function
|
func DecodeURL(uri string) string {
|
||||||
// I know it is not the best way to describe it, but I don't think you will ever need this, it is here for ANY CASE
|
u, err := url.Parse(uri)
|
||||||
func DecodeFasthttpURL(path string) string {
|
if err != nil {
|
||||||
if path == "" {
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
u := fasthttp.AcquireURI()
|
return u.String()
|
||||||
u.SetPath(path)
|
|
||||||
encodedPath := u.String()[8:]
|
|
||||||
fasthttp.ReleaseURI(u)
|
|
||||||
return encodedPath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL returns the subdomain+ host + Path(...optional named parameters if route is dynamic)
|
// URL returns the subdomain+ host + Path(...optional named parameters if route is dynamic)
|
||||||
|
@ -1146,66 +1169,14 @@ func (s *Framework) Cache(bodyHandler HandlerFunc, expiration time.Duration) Han
|
||||||
// ----------------------------------MuxAPI implementation------------------------------
|
// ----------------------------------MuxAPI implementation------------------------------
|
||||||
// -------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------
|
||||||
// -------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------
|
||||||
type (
|
|
||||||
// RouteNameFunc the func returns from the MuxAPi's methods, optionally sets the name of the Route (*route)
|
|
||||||
RouteNameFunc func(string)
|
|
||||||
// MuxAPI the visible api for the serveMux
|
|
||||||
MuxAPI interface {
|
|
||||||
Party(string, ...HandlerFunc) MuxAPI
|
|
||||||
// middleware serial, appending
|
|
||||||
Use(...Handler) MuxAPI
|
|
||||||
UseFunc(...HandlerFunc) MuxAPI
|
|
||||||
// returns itself, because at the most-cases used like .Layout, at the first-line party's declaration
|
|
||||||
Done(...Handler) MuxAPI
|
|
||||||
DoneFunc(...HandlerFunc) MuxAPI
|
|
||||||
|
|
||||||
// transactions
|
type muxAPI struct {
|
||||||
UseTransaction(...TransactionFunc) MuxAPI
|
mux *serveMux
|
||||||
DoneTransaction(...TransactionFunc) MuxAPI
|
doneMiddleware Middleware
|
||||||
|
apiRoutes []*route // used to register the .Done middleware
|
||||||
// main handlers
|
relativePath string
|
||||||
Handle(string, string, ...Handler) RouteNameFunc
|
middleware Middleware
|
||||||
HandleFunc(string, string, ...HandlerFunc) RouteNameFunc
|
}
|
||||||
API(string, HandlerAPI, ...HandlerFunc)
|
|
||||||
|
|
||||||
// http methods
|
|
||||||
Get(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Post(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Put(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Delete(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Connect(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Head(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Options(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Patch(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Trace(string, ...HandlerFunc) RouteNameFunc
|
|
||||||
Any(string, ...HandlerFunc)
|
|
||||||
|
|
||||||
// static content
|
|
||||||
StaticHandler(string, int, bool, bool, []string) HandlerFunc
|
|
||||||
Static(string, string, int) RouteNameFunc
|
|
||||||
StaticFS(string, string, int) RouteNameFunc
|
|
||||||
StaticWeb(string, string, int) RouteNameFunc
|
|
||||||
StaticServe(string, ...string) RouteNameFunc
|
|
||||||
StaticContent(string, string, []byte) RouteNameFunc
|
|
||||||
StaticEmbedded(string, string, func(string) ([]byte, error), func() []string) RouteNameFunc
|
|
||||||
Favicon(string, ...string) RouteNameFunc
|
|
||||||
|
|
||||||
// templates
|
|
||||||
Layout(string) MuxAPI // returns itself
|
|
||||||
|
|
||||||
// errors
|
|
||||||
OnError(int, HandlerFunc)
|
|
||||||
EmitError(int, *Context)
|
|
||||||
}
|
|
||||||
|
|
||||||
muxAPI struct {
|
|
||||||
mux *serveMux
|
|
||||||
doneMiddleware Middleware
|
|
||||||
apiRoutes []*route // used to register the .Done middleware
|
|
||||||
relativePath string
|
|
||||||
middleware Middleware
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ MuxAPI = &muxAPI{}
|
var _ MuxAPI = &muxAPI{}
|
||||||
|
|
||||||
|
@ -1311,9 +1282,9 @@ func (api *muxAPI) DoneFunc(handlersFn ...HandlerFunc) MuxAPI {
|
||||||
//
|
//
|
||||||
// See https://github.com/iris-contrib/examples/tree/master/transactions to manually add transactions
|
// See https://github.com/iris-contrib/examples/tree/master/transactions to manually add transactions
|
||||||
// and https://github.com/kataras/iris/blob/master/context_test.go for more
|
// and https://github.com/kataras/iris/blob/master/context_test.go for more
|
||||||
func UseTransaction(pipes ...TransactionFunc) MuxAPI {
|
// func UseTransaction(pipes ...TransactionFunc) MuxAPI {
|
||||||
return Default.UseTransaction(pipes...)
|
// return Default.UseTransaction(pipes...)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// UseTransaction adds transaction(s) middleware
|
// UseTransaction adds transaction(s) middleware
|
||||||
// the difference from manually adding them to the ctx.BeginTransaction
|
// the difference from manually adding them to the ctx.BeginTransaction
|
||||||
|
@ -1323,39 +1294,39 @@ func UseTransaction(pipes ...TransactionFunc) MuxAPI {
|
||||||
//
|
//
|
||||||
// See https://github.com/iris-contrib/examples/tree/master/transactions to manually add transactions
|
// See https://github.com/iris-contrib/examples/tree/master/transactions to manually add transactions
|
||||||
// and https://github.com/kataras/iris/blob/master/context_test.go for more
|
// and https://github.com/kataras/iris/blob/master/context_test.go for more
|
||||||
func (api *muxAPI) UseTransaction(pipes ...TransactionFunc) MuxAPI {
|
// func (api *muxAPI) UseTransaction(pipes ...TransactionFunc) MuxAPI {
|
||||||
return api.UseFunc(func(ctx *Context) {
|
// return api.UseFunc(func(ctx *Context) {
|
||||||
for i := range pipes {
|
// for i := range pipes {
|
||||||
ctx.BeginTransaction(pipes[i])
|
// ctx.BeginTransaction(pipes[i])
|
||||||
if ctx.TransactionsSkipped() {
|
// if ctx.TransactionsSkipped() {
|
||||||
ctx.StopExecution()
|
// ctx.StopExecution()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
ctx.Next()
|
// ctx.Next()
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
// DoneTransaction registers Transaction 'middleware' the only difference from .UseTransaction is that
|
// DoneTransaction registers Transaction 'middleware' the only difference from .UseTransaction is that
|
||||||
// is executed always last, after all of each route's handlers, returns itself.
|
// is executed always last, after all of each route's handlers, returns itself.
|
||||||
//
|
//
|
||||||
// See https://github.com/iris-contrib/examples/tree/master/transactions to manually add transactions
|
// See https://github.com/iris-contrib/examples/tree/master/transactions to manually add transactions
|
||||||
// and https://github.com/kataras/iris/blob/master/context_test.go for more
|
// and https://github.com/kataras/iris/blob/master/context_test.go for more
|
||||||
func DoneTransaction(pipes ...TransactionFunc) MuxAPI {
|
// func DoneTransaction(pipes ...TransactionFunc) MuxAPI {
|
||||||
return Default.DoneTransaction(pipes...)
|
// return Default.DoneTransaction(pipes...)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// DoneTransaction registers Transaction 'middleware' the only difference from .UseTransaction is that
|
// DoneTransaction registers Transaction 'middleware' the only difference from .UseTransaction is that
|
||||||
// is executed always last, after all of each route's handlers, returns itself.
|
// is executed always last, after all of each route's handlers, returns itself.
|
||||||
//
|
//
|
||||||
// See https://github.com/iris-contrib/examples/tree/master/transactions to manually add transactions
|
// See https://github.com/iris-contrib/examples/tree/master/transactions to manually add transactions
|
||||||
// and https://github.com/kataras/iris/blob/master/context_test.go for more
|
// and https://github.com/kataras/iris/blob/master/context_test.go for more
|
||||||
func (api *muxAPI) DoneTransaction(pipes ...TransactionFunc) MuxAPI {
|
// func (api *muxAPI) DoneTransaction(pipes ...TransactionFunc) MuxAPI {
|
||||||
return api.DoneFunc(func(ctx *Context) {
|
// return api.DoneFunc(func(ctx *Context) {
|
||||||
for i := range pipes {
|
// for i := range pipes {
|
||||||
ctx.BeginTransaction(pipes[i])
|
// ctx.BeginTransaction(pipes[i])
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Handle registers a route to the server's router
|
// Handle registers a route to the server's router
|
||||||
// if empty method is passed then registers handler(s) for all methods, same as .Any, but returns nil as result
|
// if empty method is passed then registers handler(s) for all methods, same as .Any, but returns nil as result
|
||||||
|
@ -1412,7 +1383,7 @@ func (api *muxAPI) Handle(method string, registedPath string, handlers ...Handle
|
||||||
if len(api.doneMiddleware) > 0 {
|
if len(api.doneMiddleware) > 0 {
|
||||||
middleware = append(middleware, api.doneMiddleware...) // register the done middleware, if any
|
middleware = append(middleware, api.doneMiddleware...) // register the done middleware, if any
|
||||||
}
|
}
|
||||||
r := api.mux.register([]byte(method), subdomain, path, middleware)
|
r := api.mux.register(method, subdomain, path, middleware)
|
||||||
api.apiRoutes = append(api.apiRoutes, r)
|
api.apiRoutes = append(api.apiRoutes, r)
|
||||||
|
|
||||||
// should we remove the api.apiRoutes on the .Party (new children party) ?, No, because the user maybe use this party later
|
// should we remove the api.apiRoutes on the .Party (new children party) ?, No, because the user maybe use this party later
|
||||||
|
@ -1541,7 +1512,7 @@ func (api *muxAPI) API(path string, restAPI HandlerAPI, middleware ...HandlerFun
|
||||||
args[0] = newController
|
args[0] = newController
|
||||||
j := 1
|
j := 1
|
||||||
|
|
||||||
ctx.VisitUserValues(func(k []byte, v interface{}) {
|
ctx.VisitValues(func(k []byte, v interface{}) {
|
||||||
if bytes.HasPrefix(k, paramPrefix) {
|
if bytes.HasPrefix(k, paramPrefix) {
|
||||||
args[j] = reflect.ValueOf(v.(string))
|
args[j] = reflect.ValueOf(v.(string))
|
||||||
|
|
||||||
|
@ -1664,136 +1635,6 @@ func (api *muxAPI) Any(registedPath string, handlersFn ...HandlerFunc) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StaticHandler returns a Handler 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 StaticHandler(systemPath string, stripSlashes int, compress bool, generateIndexPages bool, indexNames []string) HandlerFunc {
|
|
||||||
return Default.StaticHandler(systemPath, stripSlashes, compress, generateIndexPages, indexNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StaticHandler returns a Handler 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 (api *muxAPI) StaticHandler(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: StaticCacheDuration,
|
|
||||||
CompressedFileSuffix: CompressedFileSuffix,
|
|
||||||
}
|
|
||||||
|
|
||||||
if stripSlashes > 0 {
|
|
||||||
fs.PathRewrite = fasthttp.NewPathSlashesStripper(stripSlashes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create request handler for serving static files.
|
|
||||||
h := fs.NewRequestHandler()
|
|
||||||
return HandlerFunc(func(ctx *Context) {
|
|
||||||
h(ctx.RequestCtx)
|
|
||||||
errCode := ctx.RequestCtx.Response.StatusCode()
|
|
||||||
if errCode == StatusNotFound || errCode == StatusBadRequest || errCode == StatusInternalServerError {
|
|
||||||
api.mux.fireError(errCode, ctx)
|
|
||||||
}
|
|
||||||
if ctx.Pos < 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 Static(reqPath string, systemPath string, stripSlashes int) RouteNameFunc {
|
|
||||||
return Default.Static(reqPath, systemPath, stripSlashes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if / then returns /*wildcard or /something then /something/*wildcard
|
// if / then returns /*wildcard or /something then /something/*wildcard
|
||||||
// if empty then returns /*wildcard too
|
// if empty then returns /*wildcard too
|
||||||
func validateWildcard(reqPath string, paramName string) string {
|
func validateWildcard(reqPath string, paramName string) string {
|
||||||
|
@ -1809,89 +1650,6 @@ func (api *muxAPI) registerResourceRoute(reqPath string, h HandlerFunc) RouteNam
|
||||||
return api.Get(reqPath, h)
|
return api.Get(reqPath, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 (api *muxAPI) Static(reqPath string, systemPath string, stripSlashes int) RouteNameFunc {
|
|
||||||
h := api.StaticHandler(systemPath, stripSlashes, false, false, nil)
|
|
||||||
reqPath = validateWildcard(reqPath, "filepath")
|
|
||||||
return api.registerResourceRoute(reqPath, 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 StaticFS(reqPath string, systemPath string, stripSlashes int) RouteNameFunc {
|
|
||||||
return Default.StaticFS(reqPath, systemPath, stripSlashes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 (api *muxAPI) StaticFS(reqPath string, systemPath string, stripSlashes int) RouteNameFunc {
|
|
||||||
h := api.StaticHandler(systemPath, stripSlashes, true, true, nil)
|
|
||||||
reqPath = validateWildcard(reqPath, "filepath")
|
|
||||||
return api.registerResourceRoute(reqPath, 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 StaticWeb(reqPath string, systemPath string, stripSlashes int) RouteNameFunc {
|
|
||||||
return Default.StaticWeb(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: ""
|
|
||||||
// * if you don't know what to put on stripSlashes just 1
|
|
||||||
// example: https://github.com/iris-contrib/examples/tree/master/static_web
|
|
||||||
func (api *muxAPI) StaticWeb(reqPath string, systemPath string, stripSlashes int) RouteNameFunc {
|
|
||||||
hasIndex := utils.Exists(systemPath + utils.PathSeparator + "index.html")
|
|
||||||
var indexNames []string
|
|
||||||
if hasIndex {
|
|
||||||
indexNames = []string{"index.html"}
|
|
||||||
}
|
|
||||||
serveHandler := api.StaticHandler(systemPath, stripSlashes, false, !hasIndex, indexNames) // if not index.html exists then generate index.html which shows the list of files
|
|
||||||
return api.registerResourceRoute(reqPath+"*filepath", serveHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StaticServe serves a directory as web resource
|
// StaticServe serves a directory as web resource
|
||||||
// it's the simpliest form of the Static* functions
|
// it's the simpliest form of the Static* functions
|
||||||
// Almost same usage as StaticWeb
|
// Almost same usage as StaticWeb
|
||||||
|
@ -2107,16 +1865,17 @@ func (api *muxAPI) Favicon(favPath string, requestPath ...string) RouteNameFunc
|
||||||
modtime = fi.ModTime().UTC().Format(ctx.framework.Config.TimeFormat)
|
modtime = fi.ModTime().UTC().Format(ctx.framework.Config.TimeFormat)
|
||||||
}
|
}
|
||||||
if t, err := time.Parse(ctx.framework.Config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && fi.ModTime().Before(t.Add(StaticCacheDuration)) {
|
if t, err := time.Parse(ctx.framework.Config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && fi.ModTime().Before(t.Add(StaticCacheDuration)) {
|
||||||
ctx.Response.Header.Del(contentType)
|
|
||||||
ctx.Response.Header.Del(contentLength)
|
ctx.ResponseWriter.Header().Del(contentType)
|
||||||
|
ctx.ResponseWriter.Header().Del(contentLength)
|
||||||
ctx.SetStatusCode(StatusNotModified)
|
ctx.SetStatusCode(StatusNotModified)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Response.Header.Set(contentType, cType)
|
ctx.ResponseWriter.Header().Set(contentType, cType)
|
||||||
ctx.Response.Header.Set(lastModified, modtime)
|
ctx.ResponseWriter.Header().Set(lastModified, modtime)
|
||||||
ctx.SetStatusCode(StatusOK)
|
ctx.SetStatusCode(StatusOK)
|
||||||
ctx.Response.SetBody(cacheFav)
|
ctx.ResponseWriter.SetBody(cacheFav)
|
||||||
}
|
}
|
||||||
|
|
||||||
reqPath := "/favicon" + path.Ext(fi.Name()) //we could use the filename, but because standards is /favicon.ico/.png.
|
reqPath := "/favicon" + path.Ext(fi.Name()) //we could use the filename, but because standards is /favicon.ico/.png.
|
||||||
|
@ -2127,6 +1886,91 @@ func (api *muxAPI) Favicon(favPath string, requestPath ...string) RouteNameFunc
|
||||||
return api.registerResourceRoute(reqPath, h)
|
return api.registerResourceRoute(reqPath, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StripPrefix returns a handler that serves HTTP requests
|
||||||
|
// by removing the given prefix from the request URL's Path
|
||||||
|
// and invoking the handler h. StripPrefix handles a
|
||||||
|
// request for a path that doesn't begin with prefix by
|
||||||
|
// replying with an HTTP 404 not found error.
|
||||||
|
func StripPrefix(prefix string, h HandlerFunc) HandlerFunc {
|
||||||
|
if prefix == "" {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
return func(ctx *Context) {
|
||||||
|
if p := strings.TrimPrefix(ctx.Request.URL.Path, prefix); len(p) < len(ctx.Request.URL.Path) {
|
||||||
|
ctx.Request.URL.Path = p
|
||||||
|
h(ctx)
|
||||||
|
} else {
|
||||||
|
ctx.NotFound()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticHandler returns a new Handler which serves static files
|
||||||
|
func StaticHandler(reqPath string, systemPath string, showList bool, enableGzip bool) HandlerFunc {
|
||||||
|
return Default.StaticHandler(reqPath, systemPath, showList, enableGzip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticHandler returns a new Handler which serves static files
|
||||||
|
func (api *muxAPI) StaticHandler(reqPath string, systemPath string, showList bool, enableGzip bool) HandlerFunc {
|
||||||
|
h := NewStaticHandlerBuilder(systemPath).
|
||||||
|
Path(api.relativePath + reqPath).
|
||||||
|
Listing(showList).
|
||||||
|
Gzip(enableGzip).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
managedStaticHandler := func(ctx *Context) {
|
||||||
|
h(ctx)
|
||||||
|
prevStatusCode := ctx.ResponseWriter.StatusCode()
|
||||||
|
if prevStatusCode >= 400 { // we have an error
|
||||||
|
// fire the custom error handler
|
||||||
|
api.mux.fireError(prevStatusCode, ctx)
|
||||||
|
}
|
||||||
|
// go to the next middleware
|
||||||
|
if ctx.Pos < len(ctx.Middleware)-1 {
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return managedStaticHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticWeb returns a handler that serves HTTP requests
|
||||||
|
// with the contents of the file system rooted at directory.
|
||||||
|
//
|
||||||
|
// first parameter: the route path
|
||||||
|
// second parameter: the system directory
|
||||||
|
// for more options look iris.StaticHandler.
|
||||||
|
//
|
||||||
|
// iris.StaticWeb("/static", "./static")
|
||||||
|
//
|
||||||
|
// As a special case, the returned file server redirects any request
|
||||||
|
// ending in "/index.html" to the same path, without the final
|
||||||
|
// "index.html".
|
||||||
|
//
|
||||||
|
// StaticWeb calls the StaticHandler(reqPath, systemPath, listingDirectories: false, gzip: false ).
|
||||||
|
func StaticWeb(reqPath string, systemPath string) RouteNameFunc {
|
||||||
|
return Default.StaticWeb(reqPath, systemPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticWeb returns a handler that serves HTTP requests
|
||||||
|
// with the contents of the file system rooted at directory.
|
||||||
|
//
|
||||||
|
// first parameter: the route path
|
||||||
|
// second parameter: the system directory
|
||||||
|
// for more options look iris.StaticHandler.
|
||||||
|
//
|
||||||
|
// iris.StaticWeb("/static", "./static")
|
||||||
|
//
|
||||||
|
// As a special case, the returned file server redirects any request
|
||||||
|
// ending in "/index.html" to the same path, without the final
|
||||||
|
// "index.html".
|
||||||
|
//
|
||||||
|
// StaticWeb calls the StaticHandler(reqPath, systemPath, listingDirectories: false, gzip: false ).
|
||||||
|
func (api *muxAPI) StaticWeb(reqPath string, systemPath string) RouteNameFunc {
|
||||||
|
h := api.StaticHandler(reqPath, systemPath, false, false)
|
||||||
|
routePath := validateWildcard(reqPath, "file")
|
||||||
|
return api.registerResourceRoute(routePath, h)
|
||||||
|
}
|
||||||
|
|
||||||
// Layout oerrides the parent template layout with a more specific layout for this Party
|
// Layout oerrides the parent template layout with a more specific layout for this Party
|
||||||
// returns this Party, to continue as normal
|
// returns this Party, to continue as normal
|
||||||
// example:
|
// example:
|
||||||
|
@ -2215,7 +2059,7 @@ func (api *muxAPI) OnError(statusCode int, handlerFn HandlerFunc) {
|
||||||
|
|
||||||
func(statusCode int, staticPath string, prevErrHandler Handler, newHandler Handler) { // to separate the logic
|
func(statusCode int, staticPath string, prevErrHandler Handler, newHandler Handler) { // to separate the logic
|
||||||
errHandler := HandlerFunc(func(ctx *Context) {
|
errHandler := HandlerFunc(func(ctx *Context) {
|
||||||
if strings.HasPrefix(ctx.PathString(), staticPath) { // yes the user should use OnError from longest to lower static path's length in order this to work, so we can find another way, like a builder on the end.
|
if strings.HasPrefix(ctx.Path(), staticPath) { // yes the user should use OnError from longest to lower static path's length in order this to work, so we can find another way, like a builder on the end.
|
||||||
newHandler.Serve(ctx)
|
newHandler.Serve(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kataras/go-errors"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kataras/go-errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var goPath string
|
var goPath string
|
||||||
|
|
|
@ -2,12 +2,13 @@ package main // #nosec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/kataras/cli"
|
|
||||||
"github.com/kataras/go-fs"
|
|
||||||
"github.com/skratchdot/open-golang/open"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kataras/cli"
|
||||||
|
"github.com/kataras/go-fs"
|
||||||
|
"github.com/skratchdot/open-golang/open"
|
||||||
)
|
)
|
||||||
|
|
||||||
// we introduce a project type, because I'm (not near future) planning dynamic inserting projects here by iris community
|
// we introduce a project type, because I'm (not near future) planning dynamic inserting projects here by iris community
|
||||||
|
|
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/kataras/cli"
|
"github.com/kataras/cli"
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package iris
|
package iris
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
|
||||||
|
|
||||||
"log"
|
"log"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/kataras/go-errors"
|
"github.com/kataras/go-errors"
|
||||||
"github.com/kataras/go-fs"
|
"github.com/kataras/go-fs"
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
// Black-box Testing
|
// Black-box Testing
|
||||||
package iris_test
|
package iris_test
|
||||||
|
|
||||||
/*
|
|
||||||
Contains tests for plugin, no end-to-end, just local-object tests, these are enoguh for now.
|
|
||||||
|
|
||||||
CONTRIBUTE & DISCUSSION ABOUT TESTS TO: https://github.com/iris-contrib/tests
|
|
||||||
*/
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/kataras/iris"
|
"github.com/kataras/iris"
|
||||||
|
|
280
response_writer.go
Normal file
280
response_writer.go
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
package iris
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/kataras/go-errors"
|
||||||
|
"github.com/kataras/go-fs"
|
||||||
|
"github.com/klauspost/compress/gzip"
|
||||||
|
)
|
||||||
|
|
||||||
|
type gzipResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
gzipWriter *gzip.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
var gzpool = sync.Pool{New: func() interface{} { return &gzipResponseWriter{} }}
|
||||||
|
|
||||||
|
func acquireGzipResponseWriter(underline http.ResponseWriter) *gzipResponseWriter {
|
||||||
|
w := gzpool.Get().(*gzipResponseWriter)
|
||||||
|
w.ResponseWriter = underline
|
||||||
|
w.gzipWriter = fs.AcquireGzipWriter(w.ResponseWriter)
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseGzipResponseWriter(w *gzipResponseWriter) {
|
||||||
|
fs.ReleaseGzipWriter(w.gzipWriter)
|
||||||
|
gzpool.Put(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write compresses and writes that data to the underline response writer
|
||||||
|
func (w *gzipResponseWriter) Write(contents []byte) (int, error) {
|
||||||
|
return w.gzipWriter.Write(contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
var rpool = sync.Pool{New: func() interface{} { return &ResponseWriter{} }}
|
||||||
|
|
||||||
|
func acquireResponseWriter(underline http.ResponseWriter) *ResponseWriter {
|
||||||
|
w := rpool.Get().(*ResponseWriter)
|
||||||
|
w.ResponseWriter = underline
|
||||||
|
w.headers = underline.Header()
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseResponseWriter(w *ResponseWriter) {
|
||||||
|
w.headers = nil
|
||||||
|
w.ResponseWriter = nil
|
||||||
|
w.statusCode = 0
|
||||||
|
w.beforeFlush = nil
|
||||||
|
w.ResetBody()
|
||||||
|
rpool.Put(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A ResponseWriter interface is used by an HTTP handler to
|
||||||
|
// construct an HTTP response.
|
||||||
|
//
|
||||||
|
// A ResponseWriter may not be used after the Handler.ServeHTTP method
|
||||||
|
// has returned.
|
||||||
|
type ResponseWriter struct {
|
||||||
|
// yes only one callback, we need simplicity here because on EmitError the beforeFlush events should NOT be cleared
|
||||||
|
// but the response is cleared.
|
||||||
|
// Sometimes is useful to keep the event,
|
||||||
|
// so we keep one func only and let the user decide when he/she wants to override it with an empty func before the EmitError (context's behavior)
|
||||||
|
beforeFlush func()
|
||||||
|
http.ResponseWriter
|
||||||
|
// these three fields are setted on flushBody which runs only once on the end of the handler execution.
|
||||||
|
// this helps the performance on multi-write and keep tracks the body, status code and headers in order to run each transaction
|
||||||
|
// on its own
|
||||||
|
chunks []byte // keep track of the body in order to be resetable and useful inside custom transactions
|
||||||
|
statusCode int // the saved status code which will be used from the cache service
|
||||||
|
headers http.Header // the saved headers
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header returns the header map that will be sent by
|
||||||
|
// WriteHeader. Changing the header after a call to
|
||||||
|
// WriteHeader (or Write) has no effect unless the modified
|
||||||
|
// headers were declared as trailers by setting the
|
||||||
|
// "Trailer" header before the call to WriteHeader (see example).
|
||||||
|
// To suppress implicit response headers, set their value to nil.
|
||||||
|
func (w *ResponseWriter) Header() http.Header {
|
||||||
|
return w.headers
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusCode returns the status code header value
|
||||||
|
func (w *ResponseWriter) StatusCode() int {
|
||||||
|
return w.statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds the contents to the body reply, it writes the contents temporarily
|
||||||
|
// to a value in order to be flushed at the end of the request,
|
||||||
|
// this method give us the opportunity to reset the body if needed.
|
||||||
|
//
|
||||||
|
// If WriteHeader has not yet been called, Write calls
|
||||||
|
// WriteHeader(http.StatusOK) before writing the data. If the Header
|
||||||
|
// does not contain a Content-Type line, Write adds a Content-Type set
|
||||||
|
// to the result of passing the initial 512 bytes of written data to
|
||||||
|
// DetectContentType.
|
||||||
|
//
|
||||||
|
// Depending on the HTTP protocol version and the client, calling
|
||||||
|
// Write or WriteHeader may prevent future reads on the
|
||||||
|
// Request.Body. For HTTP/1.x requests, handlers should read any
|
||||||
|
// needed request body data before writing the response. Once the
|
||||||
|
// headers have been flushed (due to either an explicit Flusher.Flush
|
||||||
|
// call or writing enough data to trigger a flush), the request body
|
||||||
|
// may be unavailable. For HTTP/2 requests, the Go HTTP server permits
|
||||||
|
// handlers to continue to read the request body while concurrently
|
||||||
|
// writing the response. However, such behavior may not be supported
|
||||||
|
// by all HTTP/2 clients. Handlers should read before writing if
|
||||||
|
// possible to maximize compatibility.
|
||||||
|
func (w *ResponseWriter) Write(contents []byte) (int, error) {
|
||||||
|
w.chunks = append(w.chunks, contents...)
|
||||||
|
return len(w.chunks), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body returns the body tracked from the writer so far
|
||||||
|
// do not use this for edit.
|
||||||
|
func (w *ResponseWriter) Body() []byte {
|
||||||
|
return w.chunks
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBodyString overrides the body and sets it to a string value
|
||||||
|
func (w *ResponseWriter) SetBodyString(s string) {
|
||||||
|
w.chunks = []byte(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBody overrides the body and sets it to a slice of bytes value
|
||||||
|
func (w *ResponseWriter) SetBody(b []byte) {
|
||||||
|
w.chunks = b
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetBody resets the response body
|
||||||
|
func (w *ResponseWriter) ResetBody() {
|
||||||
|
w.chunks = w.chunks[0:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetHeaders clears the temp headers
|
||||||
|
func (w *ResponseWriter) ResetHeaders() {
|
||||||
|
// original response writer's headers are empty.
|
||||||
|
w.headers = w.ResponseWriter.Header()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the response body, headers and the status code header
|
||||||
|
func (w *ResponseWriter) Reset() {
|
||||||
|
w.ResetHeaders()
|
||||||
|
w.statusCode = 0
|
||||||
|
w.ResetBody()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader sends an HTTP response header with status code.
|
||||||
|
// If WriteHeader is not called explicitly, the first call to Write
|
||||||
|
// will trigger an implicit WriteHeader(http.StatusOK).
|
||||||
|
// Thus explicit calls to WriteHeader are mainly used to
|
||||||
|
// send error codes.
|
||||||
|
func (w *ResponseWriter) WriteHeader(statusCode int) {
|
||||||
|
w.statusCode = statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentType returns the content type, if not setted returns empty string
|
||||||
|
func (w *ResponseWriter) ContentType() string {
|
||||||
|
return w.headers.Get(contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContentType sets the content type header
|
||||||
|
func (w *ResponseWriter) SetContentType(cType string) {
|
||||||
|
w.headers.Set(contentType, cType)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errHijackNotSupported = errors.New("Hijack is not supported to this response writer!")
|
||||||
|
|
||||||
|
// Hijack lets the caller take over the connection.
|
||||||
|
// After a call to Hijack(), the HTTP server library
|
||||||
|
// will not do anything else with the connection.
|
||||||
|
//
|
||||||
|
// It becomes the caller's responsibility to manage
|
||||||
|
// and close the connection.
|
||||||
|
//
|
||||||
|
// The returned net.Conn may have read or write deadlines
|
||||||
|
// already set, depending on the configuration of the
|
||||||
|
// Server. It is the caller's responsibility to set
|
||||||
|
// or clear those deadlines as needed.
|
||||||
|
func (w *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
if h, isHijacker := w.ResponseWriter.(http.Hijacker); isHijacker {
|
||||||
|
return h.Hijack()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil, errHijackNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBeforeFlush registers the unique callback which called exactly before the response is flushed to the client
|
||||||
|
func (w *ResponseWriter) SetBeforeFlush(cb func()) {
|
||||||
|
w.beforeFlush = cb
|
||||||
|
}
|
||||||
|
|
||||||
|
// flushResponse the full body, headers and status code to the underline response writer
|
||||||
|
// called automatically at the end of each request, see ReleaseCtx
|
||||||
|
func (w *ResponseWriter) flushResponse() {
|
||||||
|
|
||||||
|
if w.beforeFlush != nil {
|
||||||
|
w.beforeFlush()
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.statusCode > 0 {
|
||||||
|
w.ResponseWriter.WriteHeader(w.statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.headers != nil {
|
||||||
|
for k, values := range w.headers {
|
||||||
|
for i := range values {
|
||||||
|
w.ResponseWriter.Header().Add(k, values[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(w.chunks) > 0 {
|
||||||
|
w.ResponseWriter.Write(w.chunks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush sends any buffered data to the client.
|
||||||
|
func (w *ResponseWriter) Flush() {
|
||||||
|
w.flushResponse()
|
||||||
|
// The Flusher interface is implemented by ResponseWriters that allow
|
||||||
|
// an HTTP handler to flush buffered data to the client.
|
||||||
|
//
|
||||||
|
// The default HTTP/1.x and HTTP/2 ResponseWriter implementations
|
||||||
|
// support Flusher, but ResponseWriter wrappers may not. Handlers
|
||||||
|
// should always test for this ability at runtime.
|
||||||
|
//
|
||||||
|
// Note that even for ResponseWriters that support Flush,
|
||||||
|
// if the client is connected through an HTTP proxy,
|
||||||
|
// the buffered data may not reach the client until the response
|
||||||
|
// completes.
|
||||||
|
if fl, isFlusher := w.ResponseWriter.(http.Flusher); isFlusher {
|
||||||
|
fl.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clone returns a clone of this response writer
|
||||||
|
// it copies the header, status code, headers and the beforeFlush finally returns a new ResponseWriter
|
||||||
|
func (w *ResponseWriter) clone() *ResponseWriter {
|
||||||
|
wc := &ResponseWriter{}
|
||||||
|
wc.ResponseWriter = w.ResponseWriter
|
||||||
|
wc.statusCode = w.statusCode
|
||||||
|
wc.headers = w.headers
|
||||||
|
wc.chunks = w.chunks[0:]
|
||||||
|
wc.beforeFlush = w.beforeFlush
|
||||||
|
return wc
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeTo writes a response writer (temp: status code, headers and body) to another response writer
|
||||||
|
func (w *ResponseWriter) writeTo(to *ResponseWriter) {
|
||||||
|
// set the status code, failure status code are first class
|
||||||
|
if w.statusCode > 0 {
|
||||||
|
to.statusCode = w.statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// append the headers
|
||||||
|
if w.headers != nil {
|
||||||
|
for k, values := range w.headers {
|
||||||
|
for _, v := range values {
|
||||||
|
if to.headers.Get(v) == "" {
|
||||||
|
to.headers.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// append the body
|
||||||
|
if len(w.chunks) > 0 {
|
||||||
|
to.Write(w.chunks)
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.beforeFlush != nil {
|
||||||
|
to.SetBeforeFlush(w.beforeFlush)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
package iris
|
package iris
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/kataras/go-fs"
|
"github.com/kataras/go-fs"
|
||||||
"github.com/kataras/go-template"
|
"github.com/kataras/go-template"
|
||||||
"io"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -105,14 +106,14 @@ func (t *templateEngines) render(isFile bool, ctx *Context, filenameOrSource str
|
||||||
|
|
||||||
var out io.Writer
|
var out io.Writer
|
||||||
if gzipEnabled && ctx.clientAllowsGzip() {
|
if gzipEnabled && ctx.clientAllowsGzip() {
|
||||||
ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
|
ctx.ResponseWriter.Header().Add(varyHeader, acceptEncodingHeader)
|
||||||
ctx.SetHeader(contentEncodingHeader, "gzip")
|
ctx.SetHeader(contentEncodingHeader, "gzip")
|
||||||
|
|
||||||
gzipWriter := fs.AcquireGzipWriter(ctx.Response.BodyWriter())
|
gzipWriter := fs.AcquireGzipWriter(ctx.ResponseWriter)
|
||||||
defer fs.ReleaseGzipWriter(gzipWriter)
|
defer fs.ReleaseGzipWriter(gzipWriter)
|
||||||
out = gzipWriter
|
out = gzipWriter
|
||||||
} else {
|
} else {
|
||||||
out = ctx.Response.BodyWriter()
|
out = ctx.ResponseWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
if isFile {
|
if isFile {
|
||||||
|
|
166
transactions.go
Normal file
166
transactions.go
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
package iris
|
||||||
|
|
||||||
|
// TransactionErrResult could be named also something like 'MaybeError',
|
||||||
|
// it is useful to send it on transaction.Complete in order to execute a custom error mesasge to the user.
|
||||||
|
//
|
||||||
|
// in simple words it's just a 'traveler message' between the transaction and its scope.
|
||||||
|
// it is totally optional
|
||||||
|
type TransactionErrResult struct {
|
||||||
|
StatusCode int
|
||||||
|
// if reason is empty then the already relative registered (custom or not)
|
||||||
|
// error will be executed if the scope allows that.
|
||||||
|
Reason string
|
||||||
|
ContentType string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the reason given by the user or an empty string
|
||||||
|
func (err TransactionErrResult) Error() string {
|
||||||
|
return err.Reason
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFailure returns true if this is an actual error
|
||||||
|
func (err TransactionErrResult) IsFailure() bool {
|
||||||
|
return err.StatusCode >= 400
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTransactionErrResult returns a new transaction result with the given error message,
|
||||||
|
// it can be empty too, but if not then the transaction's scope is decided what to do with that
|
||||||
|
func NewTransactionErrResult() TransactionErrResult {
|
||||||
|
return TransactionErrResult{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction gives the users the opportunity to code their route handlers cleaner and safier
|
||||||
|
// it receives a scope which is decided when to send an error to the user, recover from panics
|
||||||
|
// stop the execution of the next transactions and so on...
|
||||||
|
//
|
||||||
|
// it's default scope is the TransientTransactionScope which is silently
|
||||||
|
// skips the current transaction's response if transaction.Complete accepts a non-empty error.
|
||||||
|
//
|
||||||
|
// Create and set custom transactions scopes with transaction.SetScope.
|
||||||
|
//
|
||||||
|
// For more information please view the tests
|
||||||
|
type Transaction struct {
|
||||||
|
Context *Context
|
||||||
|
parent *Context
|
||||||
|
hasError bool
|
||||||
|
scope TransactionScope
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTransaction(from *Context) *Transaction {
|
||||||
|
tempCtx := *from
|
||||||
|
writer := tempCtx.ResponseWriter.clone()
|
||||||
|
tempCtx.ResponseWriter = writer
|
||||||
|
t := &Transaction{
|
||||||
|
parent: from,
|
||||||
|
Context: &tempCtx,
|
||||||
|
scope: TransientTransactionScope,
|
||||||
|
}
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetScope sets the current transaction's scope
|
||||||
|
// iris.RequestTransactionScope || iris.TransientTransactionScope (default)
|
||||||
|
func (t *Transaction) SetScope(scope TransactionScope) {
|
||||||
|
t.scope = scope
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete completes the transaction
|
||||||
|
// rollback and send an error when the error is not empty.
|
||||||
|
// The next steps depends on its Scope.
|
||||||
|
//
|
||||||
|
// The error can be a type of NewTransactionErrResult()
|
||||||
|
func (t *Transaction) Complete(err error) {
|
||||||
|
maybeErr := TransactionErrResult{}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.hasError = true
|
||||||
|
|
||||||
|
statusCode := StatusBadRequest
|
||||||
|
reason := err.Error()
|
||||||
|
cType := "text/plain; charset=" + t.Context.framework.Config.Charset
|
||||||
|
|
||||||
|
if errWstatus, ok := err.(TransactionErrResult); ok {
|
||||||
|
if errWstatus.StatusCode > 0 {
|
||||||
|
statusCode = errWstatus.StatusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if errWstatus.Reason != "" {
|
||||||
|
reason = errWstatus.Reason
|
||||||
|
}
|
||||||
|
// get the content type used on this transaction
|
||||||
|
if cTypeH := t.Context.ResponseWriter.Header().Get(contentType); cTypeH != "" {
|
||||||
|
cType = cTypeH
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
maybeErr.StatusCode = statusCode
|
||||||
|
maybeErr.Reason = reason
|
||||||
|
maybeErr.ContentType = cType
|
||||||
|
}
|
||||||
|
// the transaction ends with error or not error, it decides what to do next with its Response
|
||||||
|
// the Response is appended to the parent context an all cases but it checks for empty body,headers and all that,
|
||||||
|
// if they are empty (silent error or not error at all)
|
||||||
|
// then all transaction's actions are skipped as expected
|
||||||
|
canContinue := t.scope.EndTransaction(maybeErr, t.Context)
|
||||||
|
if !canContinue {
|
||||||
|
t.parent.SkipTransactions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionScope is the manager of the transaction's response, can be resseted and skipped
|
||||||
|
// from its parent context or execute an error or skip other transactions
|
||||||
|
type TransactionScope interface {
|
||||||
|
// EndTransaction returns if can continue to the next transactions or not (false)
|
||||||
|
// called after Complete, empty or not empty error
|
||||||
|
EndTransaction(maybeErr TransactionErrResult, ctx *Context) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionScopeFunc the transaction's scope signature
|
||||||
|
type TransactionScopeFunc func(maybeErr TransactionErrResult, ctx *Context) bool
|
||||||
|
|
||||||
|
// EndTransaction ends the transaction with a callback to itself, implements the TransactionScope interface
|
||||||
|
func (tsf TransactionScopeFunc) EndTransaction(maybeErr TransactionErrResult, ctx *Context) bool {
|
||||||
|
return tsf(maybeErr, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransientTransactionScope explaination:
|
||||||
|
//
|
||||||
|
// independent 'silent' scope, if transaction fails (if transaction.IsFailure() == true)
|
||||||
|
// then its response is not written to the real context no error is provided to the user.
|
||||||
|
// useful for the most cases.
|
||||||
|
var TransientTransactionScope = TransactionScopeFunc(func(maybeErr TransactionErrResult, ctx *Context) bool {
|
||||||
|
if maybeErr.IsFailure() {
|
||||||
|
ctx.ResponseWriter.Reset() // this response is skipped because it's empty.
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// RequestTransactionScope explaination:
|
||||||
|
//
|
||||||
|
// if scope fails (if transaction.IsFailure() == true)
|
||||||
|
// then the rest of the context's response (transaction or normal flow)
|
||||||
|
// is not written to the client, and an error status code is written instead.
|
||||||
|
var RequestTransactionScope = TransactionScopeFunc(func(maybeErr TransactionErrResult, ctx *Context) bool {
|
||||||
|
if maybeErr.IsFailure() {
|
||||||
|
// we need to register a beforeResponseFlush event here in order
|
||||||
|
// to execute last the EmitError
|
||||||
|
// (which will reset the whole response's body, status code and headers setted from normal flow or other transactions too)
|
||||||
|
ctx.ResponseWriter.SetBeforeFlush(func() {
|
||||||
|
if maybeErr.Reason != "" {
|
||||||
|
ctx.ResponseWriter.Reset()
|
||||||
|
// send the error with the info user provided
|
||||||
|
ctx.ResponseWriter.SetBodyString(maybeErr.Reason)
|
||||||
|
ctx.ResponseWriter.WriteHeader(maybeErr.StatusCode)
|
||||||
|
ctx.ResponseWriter.SetContentType(maybeErr.ContentType)
|
||||||
|
} else {
|
||||||
|
// else execute the registered user error and skip the next transactions and all normal flow,
|
||||||
|
ctx.EmitError(maybeErr.StatusCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
149
webfs.go
Normal file
149
webfs.go
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
package iris
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StaticHandlerBuilder is the web file system's Handler builder
|
||||||
|
// use that or the iris.StaticHandler/StaticWeb methods
|
||||||
|
type StaticHandlerBuilder interface {
|
||||||
|
Path(requestRoutePath string) StaticHandlerBuilder
|
||||||
|
Gzip(enable bool) StaticHandlerBuilder
|
||||||
|
Listing(listDirectoriesOnOff bool) StaticHandlerBuilder
|
||||||
|
StripPath(yesNo bool) StaticHandlerBuilder
|
||||||
|
Build() HandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
type webfs struct {
|
||||||
|
// user options, only directory is required.
|
||||||
|
directory http.Dir
|
||||||
|
requestPath string
|
||||||
|
stripPath bool
|
||||||
|
gzip bool
|
||||||
|
listDirectories bool
|
||||||
|
// these are init on the Build() call
|
||||||
|
filesystem http.FileSystem
|
||||||
|
once sync.Once
|
||||||
|
handler HandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func toWebPath(systemPath string) string {
|
||||||
|
// winos slash to slash
|
||||||
|
webpath := strings.Replace(systemPath, "\\", slash, -1)
|
||||||
|
// double slashes to single
|
||||||
|
webpath = strings.Replace(webpath, slash+slash, slash, -1)
|
||||||
|
// remove all dots
|
||||||
|
webpath = strings.Replace(webpath, ".", "", -1)
|
||||||
|
return webpath
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStaticHandlerBuilder returns a new Handler which serves static files
|
||||||
|
// supports gzip, no listing and much more
|
||||||
|
// Note that, this static builder returns a Handler
|
||||||
|
// it doesn't cares about the rest of your iris configuration.
|
||||||
|
//
|
||||||
|
// Use the iris.StaticHandler/StaticWeb in order to serve static files on more automatic way
|
||||||
|
// this builder is used by people who have more complicated application
|
||||||
|
// structure and want a fluent api to work on.
|
||||||
|
func NewStaticHandlerBuilder(dir string) StaticHandlerBuilder {
|
||||||
|
return &webfs{
|
||||||
|
directory: http.Dir(dir),
|
||||||
|
// default route path is the same as the directory
|
||||||
|
requestPath: toWebPath(dir),
|
||||||
|
// enable strip path by-default
|
||||||
|
stripPath: true,
|
||||||
|
// gzip is disabled by default
|
||||||
|
gzip: false,
|
||||||
|
// list directories disabled by default
|
||||||
|
listDirectories: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path sets the request path.
|
||||||
|
// Defaults to same as system path
|
||||||
|
func (w *webfs) Path(requestRoutePath string) StaticHandlerBuilder {
|
||||||
|
w.requestPath = toWebPath(requestRoutePath)
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gzip if enable is true then gzip compression is enabled for this static directory
|
||||||
|
// Defaults to false
|
||||||
|
func (w *webfs) Gzip(enable bool) StaticHandlerBuilder {
|
||||||
|
w.gzip = enable
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listing turn on/off the 'show files and directories'.
|
||||||
|
// Defaults to false
|
||||||
|
func (w *webfs) Listing(listDirectoriesOnOff bool) StaticHandlerBuilder {
|
||||||
|
w.listDirectories = listDirectoriesOnOff
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *webfs) StripPath(yesNo bool) StaticHandlerBuilder {
|
||||||
|
w.stripPath = yesNo
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
noListFile struct {
|
||||||
|
http.File
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Overrides the Readdir of the http.File in order to disable showing a list of the dirs/files
|
||||||
|
func (n noListFile) Readdir(count int) ([]os.FileInfo, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the http.Filesystem
|
||||||
|
// Do not call it.
|
||||||
|
func (w *webfs) Open(name string) (http.File, error) {
|
||||||
|
info, err := w.filesystem.Open(name)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !w.listDirectories {
|
||||||
|
return noListFile{info}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the handler (once) and returns it
|
||||||
|
func (w *webfs) Build() HandlerFunc {
|
||||||
|
// we have to ensure that Build is called ONLY one time,
|
||||||
|
// one instance per one static directory.
|
||||||
|
w.once.Do(func() {
|
||||||
|
w.filesystem = http.Dir(w.directory)
|
||||||
|
|
||||||
|
// set the filesystem to itself in order to be recognised of listing property (can be change at runtime too)
|
||||||
|
fileserver := http.FileServer(w)
|
||||||
|
fsHandler := fileserver
|
||||||
|
if w.stripPath {
|
||||||
|
prefix := w.requestPath
|
||||||
|
fsHandler = http.StripPrefix(prefix, fileserver)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.handler = func(ctx *Context) {
|
||||||
|
writer := ctx.ResponseWriter.ResponseWriter
|
||||||
|
|
||||||
|
if w.gzip && ctx.clientAllowsGzip() {
|
||||||
|
ctx.ResponseWriter.Header().Add(varyHeader, acceptEncodingHeader)
|
||||||
|
ctx.SetHeader(contentEncodingHeader, "gzip")
|
||||||
|
gzipResWriter := acquireGzipResponseWriter(ctx.ResponseWriter.ResponseWriter)
|
||||||
|
writer = gzipResWriter
|
||||||
|
defer releaseGzipResponseWriter(gzipResWriter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fsHandler.ServeHTTP(writer, ctx.Request)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return w.handler
|
||||||
|
}
|
135
websocket.go
135
websocket.go
|
@ -1,7 +1,9 @@
|
||||||
package iris
|
package iris
|
||||||
|
|
||||||
import (
|
import (
|
||||||
irisWebsocket "github.com/iris-contrib/websocket"
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/kataras/go-websocket"
|
"github.com/kataras/go-websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,85 +24,52 @@ type (
|
||||||
// the below code is a wrapper and bridge between iris-contrib/websocket and kataras/go-websocket
|
// the below code is a wrapper and bridge between iris-contrib/websocket and kataras/go-websocket
|
||||||
WebsocketServer struct {
|
WebsocketServer struct {
|
||||||
websocket.Server
|
websocket.Server
|
||||||
upgrader irisWebsocket.Upgrader
|
station *Framework
|
||||||
|
once sync.Once
|
||||||
// the only fields we need at runtime here for iris-specific error and check origin funcs
|
// Config:
|
||||||
// they comes from WebsocketConfiguration
|
// if endpoint is not empty then this configuration is used instead of the station's
|
||||||
|
// useful when the user/dev wants more than one websocket server inside one iris instance.
|
||||||
// Error specifies the function for generating HTTP error responses.
|
Config WebsocketConfiguration
|
||||||
Error func(ctx *Context, status int, reason string)
|
|
||||||
// CheckOrigin returns true if the request Origin header is acceptable. If
|
|
||||||
// CheckOrigin is nil, the host in the Origin header must not be set or
|
|
||||||
// must match the host of the request.
|
|
||||||
CheckOrigin func(ctx *Context) bool
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewWebsocketServer returns an empty WebsocketServer, nothing special here.
|
// NewWebsocketServer returns a new empty unitialized websocket server
|
||||||
func NewWebsocketServer() *WebsocketServer {
|
// it runs on first OnConnection
|
||||||
return &WebsocketServer{}
|
func NewWebsocketServer(station *Framework) *WebsocketServer {
|
||||||
|
return &WebsocketServer{station: station, Server: websocket.New()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
|
// NewWebsocketServer creates the client side source route and the route path Endpoint with the correct Handler
|
||||||
//
|
// receives the websocket configuration and the iris station
|
||||||
// The responseHeader is included in the response to the client's upgrade
|
// and returns the websocket server which can be attached to more than one iris station (if needed)
|
||||||
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
|
func (ws *WebsocketServer) init() {
|
||||||
// application negotiated subprotocol (Sec-Websocket-Protocol).
|
if ws.Config.Endpoint == "" {
|
||||||
//
|
ws.Config = ws.station.Config.Websocket
|
||||||
// If the upgrade fails, then Upgrade replies to the client with an HTTP error
|
}
|
||||||
// response.
|
|
||||||
func (s *WebsocketServer) Upgrade(ctx *Context) error {
|
|
||||||
return s.upgrader.Upgrade(ctx.RequestCtx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler is the iris Handler to upgrade the request
|
c := ws.Config
|
||||||
// used inside RegisterRoutes
|
|
||||||
func (s *WebsocketServer) Handler(ctx *Context) {
|
if c.Endpoint == "" {
|
||||||
// first, check origin
|
|
||||||
if !s.CheckOrigin(ctx) {
|
|
||||||
s.Error(ctx, StatusForbidden, "websocket: origin not allowed")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// all other errors comes from the underline iris-contrib/websocket
|
|
||||||
if err := s.Upgrade(ctx); err != nil {
|
|
||||||
if ctx.framework.Config.IsDevelopment {
|
|
||||||
ctx.Log("Websocket error while trying to Upgrade the connection. Trace: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
statusErrCode := StatusBadRequest
|
|
||||||
if herr, isHandshake := err.(irisWebsocket.HandshakeError); isHandshake {
|
|
||||||
statusErrCode = herr.Status()
|
|
||||||
}
|
|
||||||
// if not handshake error just fire the custom(if any) StatusBadRequest
|
|
||||||
// with the websocket's error message in the ctx.Get("WsError")
|
|
||||||
DefaultWebsocketError(ctx, statusErrCode, err.Error())
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterTo creates the client side source route and the route path Endpoint with the correct Handler
|
|
||||||
// receives the websocket configuration and the iris station
|
|
||||||
func (s *WebsocketServer) RegisterTo(station *Framework, c WebsocketConfiguration) {
|
|
||||||
|
|
||||||
// Note: s.Server should be initialize on the first OnConnection, which is called before this func when Default websocket server.
|
|
||||||
// When not: when calling this function before OnConnection, when we have more than one websocket server running
|
|
||||||
if s.Server == nil {
|
|
||||||
s.Server = websocket.New()
|
|
||||||
}
|
|
||||||
// is just a conversional type for kataras/go-websocket.Connection
|
|
||||||
s.upgrader = irisWebsocket.Custom(s.Server.HandleConnection, c.ReadBufferSize, c.WriteBufferSize, c.Headers)
|
|
||||||
|
|
||||||
// set the routing for client-side source (javascript) (optional)
|
// set the routing for client-side source (javascript) (optional)
|
||||||
clientSideLookupName := "iris-websocket-client-side"
|
clientSideLookupName := "iris-websocket-client-side"
|
||||||
station.Get(c.Endpoint, s.Handler)
|
ws.station.Get(c.Endpoint, ToHandler(ws.Server.Handler()))
|
||||||
// check if client side already exists
|
// check if client side already exists
|
||||||
if station.Lookup(clientSideLookupName) == nil {
|
if ws.station.Lookup(clientSideLookupName) == nil {
|
||||||
// serve the client side on domain:port/iris-ws.js
|
// serve the client side on domain:port/iris-ws.js
|
||||||
station.StaticContent("/iris-ws.js", contentJavascript, websocket.ClientSource)(clientSideLookupName)
|
ws.station.StaticContent("/iris-ws.js", contentJavascript, websocket.ClientSource)(clientSideLookupName)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Server.Set(websocket.Config{
|
if c.CheckOrigin == nil {
|
||||||
|
c.CheckOrigin = DefaultWebsocketCheckOrigin
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Error == nil {
|
||||||
|
c.Error = DefaultWebsocketError
|
||||||
|
}
|
||||||
|
// set the underline websocket server's configuration
|
||||||
|
ws.Server.Set(websocket.Config{
|
||||||
WriteTimeout: c.WriteTimeout,
|
WriteTimeout: c.WriteTimeout,
|
||||||
PongTimeout: c.PongTimeout,
|
PongTimeout: c.PongTimeout,
|
||||||
PingPeriod: c.PingPeriod,
|
PingPeriod: c.PingPeriod,
|
||||||
|
@ -108,22 +77,13 @@ func (s *WebsocketServer) RegisterTo(station *Framework, c WebsocketConfiguratio
|
||||||
BinaryMessages: c.BinaryMessages,
|
BinaryMessages: c.BinaryMessages,
|
||||||
ReadBufferSize: c.ReadBufferSize,
|
ReadBufferSize: c.ReadBufferSize,
|
||||||
WriteBufferSize: c.WriteBufferSize,
|
WriteBufferSize: c.WriteBufferSize,
|
||||||
|
Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
|
||||||
|
ctx := ws.station.AcquireCtx(w, r)
|
||||||
|
c.Error(ctx, status, reason)
|
||||||
|
ws.station.ReleaseCtx(ctx)
|
||||||
|
},
|
||||||
|
CheckOrigin: c.CheckOrigin,
|
||||||
})
|
})
|
||||||
|
|
||||||
s.Error = c.Error
|
|
||||||
s.CheckOrigin = c.CheckOrigin
|
|
||||||
|
|
||||||
if s.Error == nil {
|
|
||||||
s.Error = DefaultWebsocketError
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.CheckOrigin == nil {
|
|
||||||
s.CheckOrigin = DefaultWebsocketCheckOrigin
|
|
||||||
}
|
|
||||||
|
|
||||||
// run the ws server
|
|
||||||
s.Server.Serve()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebsocketConnection is the front-end API that you will use to communicate with the client side
|
// WebsocketConnection is the front-end API that you will use to communicate with the client side
|
||||||
|
@ -132,15 +92,10 @@ type WebsocketConnection interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnConnection this is the main event you, as developer, will work with each of the websocket connections
|
// OnConnection this is the main event you, as developer, will work with each of the websocket connections
|
||||||
func (s *WebsocketServer) OnConnection(connectionListener func(WebsocketConnection)) {
|
func (ws *WebsocketServer) OnConnection(connectionListener func(WebsocketConnection)) {
|
||||||
if s.Server == nil {
|
ws.once.Do(ws.init)
|
||||||
// for default webserver this is the time when the websocket server will be init
|
|
||||||
// let's initialize here the ws server, the user/dev is free to change its config before this step.
|
ws.Server.OnConnection(func(c websocket.Connection) {
|
||||||
s.Server = websocket.New() // we need that in order to use the Iris' WebsocketConnnection, which
|
|
||||||
// config is empty here because are setted on the RegisterTo
|
|
||||||
// websocket's configuration is optional on New because it doesn't really used before the websocket.Serve
|
|
||||||
}
|
|
||||||
s.Server.OnConnection(func(c websocket.Connection) {
|
|
||||||
connectionListener(c)
|
connectionListener(c)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user