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:
Gerasimos (Makis) Maropoulos 2017-01-02 21:20:17 +02:00
parent ced2083ab3
commit 8bbd9f8fc5
25 changed files with 2067 additions and 2213 deletions

View File

@ -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

View File

@ -1,4 +1,4 @@
- Version : **5.0.4** - Version : **6.0.0**
- Operating System: - Operating System:

View File

@ -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

View File

@ -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&currency_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&currency_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**

View File

@ -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
View File

@ -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
View File

@ -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" />
&nbsp;&nbsp;This project started ~9 months ago<br/>
&nbsp;&nbsp;and rapidly won your trust,<br/>
&nbsp;&nbsp;I'm very thankful for this and<br/>
&nbsp;&nbsp;I promise you that I'll continue to<br/>
&nbsp;&nbsp;do my bests.<br/><br/>
&nbsp;&nbsp;All people, poor or rich, should give<br/>
&nbsp;&nbsp;and share with each others.
<br/><br/>
&nbsp;&nbsp;As a person who knows<br/>
&nbsp;&nbsp;how someone feels when opens<br/>
&nbsp;&nbsp;the fridge and finds nothing to eat, again, <br/>
&nbsp;&nbsp;I decided that all the <a href="https://github.com/kataras/iris/blob/master/DONATIONS.md">money you<br/>
&nbsp;&nbsp;donated so far[<i>424 EUR</i>]</a>,<br/>
&nbsp;&nbsp;and until the end of this month,<br/>
&nbsp;&nbsp;<b>should go back to the people<br/>
&nbsp;&nbsp;who need them most.</b><br/>
&nbsp;&nbsp;Is not enough but...
<br/>
<br/>
&nbsp;&nbsp;CHRISTMAS IS MOST TRULY<br/>
&nbsp;&nbsp;CHRISTMAS WHEN WE<br/>
&nbsp;&nbsp;CELEBRATE IT BY GIVING THE<br/>
&nbsp;&nbsp;LIGHT OF LOVE TO THOSE<br/>
&nbsp;&nbsp;WHO NEED IT MOST.
<br/><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;~ 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

View File

@ -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,

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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
View File

@ -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() }
} }

View File

@ -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)
} }

View File

@ -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
View File

@ -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
} }

View File

@ -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

View File

@ -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

View File

@ -2,7 +2,6 @@ package main
import ( import (
"os" "os"
"strings" "strings"
"github.com/kataras/cli" "github.com/kataras/cli"

View File

@ -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"

View File

@ -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
View 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)
}
}

View File

@ -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
View 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
View 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
}

View File

@ -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)
}) })
} }