From 8bbd9f8fc5489bd72336bba732a21df361707ce2 Mon Sep 17 00:00:00 2001
From: "Gerasimos (Makis) Maropoulos"
Date: Mon, 2 Jan 2017 21:20:17 +0200
Subject: [PATCH] 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
---
.github/CONTRIBUTING.md | 4 +-
.github/ISSUE_TEMPLATE.md | 2 +-
.github/PULL_REQUEST_TEMPLATE.md | 4 -
DONATIONS.md | 27 +-
HISTORY.md | 52 ++
LICENSE | 44 +-
README.md | 247 ++-----
configuration.go | 228 +++---
configuration_test.go | 3 +-
context.go | 1138 ++++++++++++++----------------
context_test.go | 410 ++---------
http.go | 442 +++++-------
http_test.go | 222 +++---
httptest/httptest.go | 36 +-
iris.go | 662 +++++++----------
iris/fs.go | 3 +-
iris/get.go | 7 +-
iris/run.go | 1 -
plugin.go | 3 +-
plugin_test.go | 6 -
response_writer.go | 280 ++++++++
template.go | 9 +-
transactions.go | 166 +++++
webfs.go | 149 ++++
websocket.go | 135 ++--
25 files changed, 2067 insertions(+), 2213 deletions(-)
create mode 100644 response_writer.go
create mode 100644 transactions.go
create mode 100644 webfs.go
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index fee6ef52..5f078b73 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -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
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 714311b8..8a910f6e 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,4 +1,4 @@
-- Version : **5.0.4**
+- Version : **6.0.0**
- Operating System:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index f31ceaf4..12daacd0 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -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).
-
-##### 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
diff --git a/DONATIONS.md b/DONATIONS.md
index f4af6738..72b1dda6 100644
--- a/DONATIONS.md
+++ b/DONATIONS.md
@@ -1,11 +1,6 @@
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
[![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=kataras2006%40hotmail%2ecom&lc=GR&item_name=Iris%20web%20framework&item_number=iriswebframeworkdonationid2016¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)
@@ -27,7 +22,7 @@ I'm grateful for all the generous donations. Iris is fully funded by these dona
#### Donations
-- 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
@@ -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
-- 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
-- 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
-- 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
-- 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.
#### Report, so far
- 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**
diff --git a/HISTORY.md b/HISTORY.md
index 3d2de1ef..96e4e4e4 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -2,6 +2,58 @@
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
+
+## 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
- **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*
diff --git a/LICENSE b/LICENSE
index 9ac02506..2935ad5d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,31 +1,21 @@
-Copyright (c) 2016 Gerasimos Maropoulos.
-All rights reserved.
+The MIT License (MIT)
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
+Copyright (c) 2016-2017 Gerasimos Maropoulos
-1. Redistributions of source code must retain the above copyright notice, this
-list of conditions and the following disclaimer.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
-2. Redistributions in binary form must reproduce the above copyright notice, this
-list of conditions and the following disclaimer in the documentation
-and/or other materials provided with the distribution.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
-3. Neither the name of the copyright holder nor the names of its contributors may
-be used to endorse or promote products derived from this software without
-specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
-AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
-BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-IN NO EVENT SHALL THE COPYRIGHT 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.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index ebfeba1a..348d975b 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@
-
+
@@ -29,7 +29,7 @@
-Iris is the fastest back-end web framework written in Go.
+Iris is the fastest HTTP/2 web framework written in Go.
Easy to learn while it's highly customizable,
ideally suited for
both experienced and novice developers.
@@ -39,8 +39,6 @@ Besides the fact that Iris is faster than any alternatives you may met before, <
If you're coming from Node.js world, this is the expressjs alternative for the Go Programming Language.
-
-
@@ -49,10 +47,14 @@ Feature Overview
-----------
- 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
- Robust routing and middleware ecosystem
- Build RESTful APIs
-- Request-Scoped Transactions
+- Context Scoped Transactions
- Group API's and subdomains with wildcard support
- Body binding for JSON, XML, Forms, can be extended to use your own custom binders
- More than 50 handy functions to send HTTP responses
@@ -61,9 +63,8 @@ Feature Overview
- Graceful shutdown
- Limit request body
- Localization i18N
-- Serve static files
-- Cache
-- Log requests
+- Serve static files, directories and streams
+- Fast Cache System
- Customizable format and output for the logger
- Customizable HTTP errors
- Compression (Gzip)
@@ -71,22 +72,15 @@ Feature Overview
- OAuth, OAuth2 supporting 27+ popular websites
- JWT
- Basic Authentication
- - HTTP Sessions
+ - HTTP Sessions and flash messages
- Add / Remove trailing slash from the URL with option to redirect
-- Redirect requests
- - HTTP to HTTPS
- - HTTP to HTTPS WWW
- - HTTP to HTTPS non WWW
- - Non WWW to WWW
- - WWW to non WWW
+- Redirect any request
- Highly scalable rich content render (Markdown, JSON, JSONP, XML...)
-- Websocket-only API similar to socket.io
-- Hot Reload on source code changes
+- Websocket API similar to socket.io
- Typescript integration + Web IDE
-- Checks for updates at startup
-- Highly customizable
+- Optional updater
- Feels like you used iris forever, thanks to its Fluent API
-- And many others...
+- And more...
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.
-### Merry Christmas!
-
-
-
-
-
- This project started ~9 months ago
- and rapidly won your trust,
- I'm very thankful for this and
- I promise you that I'll continue to
- do my bests.
-
- All people, poor or rich, should give
- and share with each others.
-
- As a person who knows
- how someone feels when opens
- the fridge and finds nothing to eat, again,
- I decided that all the money you
- donated so far[424 EUR],
- and until the end of this month,
- should go back to the people
- who need them most.
- Is not enough but...
-
-
-
-
-
-
- CHRISTMAS IS MOST TRULY
- CHRISTMAS WHEN WE
- CELEBRATE IT BY GIVING THE
- LIGHT OF LOVE TO THOSE
- WHO NEED IT MOST.
-
- ~ Ruth Carter Stapleton
-
-
-
-
-
### New
@@ -190,7 +142,7 @@ app := iris.New()
app.Listen(....)
// New with configuration struct
-app := iris.New(iris.Configuration{ DisablePathEscape: true})
+app := iris.New(iris.Configuration{ IsDevelopment: true})
app.Listen(...)
@@ -198,7 +150,9 @@ app.Listen(...)
iris.Listen(...)
// 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(...)
```
@@ -270,8 +224,8 @@ func getProduct(ctx *iris.Context){
```go
func details(ctx *iris.Context){
- color:= ctx.URLParam("color")
- weight:= ctx.URLParamInt("weight")
+ color := ctx.URLParam("color")
+ weight,_ := ctx.URLParamInt("weight")
}
```
@@ -289,8 +243,8 @@ email | kataras2006@homail.com
```go
func save(ctx *iris.Context) {
// Get name and email
- name := ctx.FormValueString("name")
- email := ctx.FormValueString("email")
+ name := ctx.FormValue("name")
+ email := ctx.FormValue("email")
}
```
@@ -307,22 +261,16 @@ avatar | avatar
```go
func save(ctx *iris.Context) {
// Get name and email
- name := ctx.FormValueString("name")
- email := ctx.FormValueString("email")
+ name := ctx.FormValue("name")
+ email := ctx.FormValue("email")
// Get avatar
- avatar, err := ctx.FormFile("avatar")
+ avatar, info, err := ctx.FormFile("avatar")
if err != nil {
ctx.EmitError(iris.StatusInternalServerError)
return
}
- // Source
- src, err := avatar.Open()
- if err != nil {
- ctx.EmitError(iris.StatusInternalServerError)
- return
- }
- defer src.Close()
+ defer avatar.Close()
// Destination
dst, err := os.Create(avatar.Filename)
@@ -333,7 +281,7 @@ func save(ctx *iris.Context) {
defer dst.Close()
// Copy
- if _, err = io.Copy(dst, src); err != nil {
+ if _, err = io.Copy(dst, avatar); err != nil {
ctx.EmitError(iris.StatusInternalServerError)
return
}
@@ -394,13 +342,13 @@ import (
func main() {
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
ctx.Log("http status: 500 happened!")
})
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!")
})
@@ -422,85 +370,44 @@ func main() {
### 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
-// StaticHandler returns a HandlerFunc to serve static system directory
-// Accepts 5 parameters
+// Favicon serves static favicon
+// accepts 2 parameters, second is optional
+// favPath (string), declare the system directory path of the __.ico
+// requestPath (string), it's the route's path, by default this is the "/favicon.ico" because some browsers tries to get this by default first,
+// you can declare your own path if you have more than one favicon (desktop, mobile and so on)
//
-// first param is the systemPath (string)
-// Path to the root directory to serve files from.
+// this func will add a route for you which will static serve the /yuorpath/yourfile.ico to the /yourfile.ico (nothing special that you can't handle by yourself)
+// Note that you have to call it on every favicon you have to serve automatically (dekstop, mobile and so on)
//
-// second is the stripSlashes (int) level
-// * stripSlashes = 0, original path: "/foo/bar", result: "/foo/bar"
-// * stripSlashes = 1, original path: "/foo/bar", result: "/bar"
-// * stripSlashes = 2, original path: "/foo/bar", result: ""
-//
-// third is the compress (bool)
-// Transparently compresses responses if set to true.
-//
-// The server tries minimizing CPU usage by caching compressed files.
-// It adds FSCompressedFileSuffix suffix to the original file name and
-// tries saving the resulting compressed file under the new file name.
-// So it is advisable to give the server write access to Root
-// and to all inner folders in order to minimze CPU usage when serving
-// compressed responses.
-//
-// fourth is the generateIndexPages (bool)
-// Index pages for directories without files matching IndexNames
-// are automatically generated if set.
-//
-// Directory index generation may be quite slow for directories
-// with many files (more than 1K), so it is discouraged enabling
-// index pages' generation for such directories.
-//
-// fifth is the indexNames ([]string)
-// List of index file names to try opening during directory access.
-//
-// For example:
-//
-// * index.html
-// * index.htm
-// * my-super-index.xml
-//
-StaticHandler(systemPath string, stripSlashes int, compress bool,
- generateIndexPages bool, indexNames []string) HandlerFunc
+// panics on error
+Favicon(favPath string, requestPath ...string) RouteNameFunc
-// 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: ""
-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)
+// StaticHandler returns a new Handler which serves static files
+StaticHandler(reqPath string, systemPath string, showList bool, enableGzip bool) HandlerFunc
// StaticWeb same as Static but if index.html e
// xists and request uri is '/' then display the index.html's contents
// accepts three parameters
// first parameter is the request url path (string)
// second parameter is the system directory (string)
-// 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: ""
-StaticWeb(relative string, systemPath string, stripSlashes int)
+StaticWeb(reqPath string, systemPath string) RouteNameFunc
+
+// StaticEmbedded used when files are distrubuted inside the app executable, using go-bindata mostly
+// First parameter is the request path, the path which the files in the vdir will be served to, for example "/static"
+// Second parameter is the (virtual) directory path, for example "./assets"
+// Third parameter is the Asset function
+// Forth parameter is the AssetNames function
+//
+// For more take a look at the
+// example: https://github.com/iris-contrib/examples/tree/master/static_files_embedded
+StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) RouteNameFunc
+
+// StaticContent serves bytes, memory cached, on the reqPath
+// a good example of this is how the websocket server uses that to auto-register the /iris-ws.js
+StaticContent(reqPath string, cType string, content []byte) RouteNameFunc
// StaticServe serves a directory as web resource
// it's the simpliest form of the Static* functions
@@ -514,20 +421,16 @@ StaticServe(systemPath string, requestPath ...string)
```
```go
-iris.Static("/public", "./static/assets/", 1)
+iris.StaticWeb("/public", "./static/assets/")
//-> /public/assets/favicon.ico
```
```go
-iris.StaticFS("/ftp", "./myfiles/public", 1)
+iris.StaticWeb("/","./my_static_html_website")
```
```go
-iris.StaticWeb("/","./my_static_html_website", 1)
-```
-
-```go
-StaticServe(systemPath string, requestPath ...string)
+context.StaticServe(systemPath string, requestPath ...string)
```
#### Manual static file serving
@@ -540,7 +443,7 @@ StaticServe(systemPath string, requestPath ...string)
// gzipCompression (bool)
//
// 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
@@ -673,7 +576,7 @@ The web application uses the session id as the key for retrieving the stored dat
```go
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) {
@@ -682,7 +585,7 @@ iris.Get("/", func(ctx *iris.Context) {
ctx.Session().Set("name", "iris")
//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) {
@@ -690,7 +593,7 @@ iris.Get("/", func(ctx *iris.Context) {
// returns an empty string if the key was not found.
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) {
@@ -707,7 +610,7 @@ iris.Get("/", func(ctx *iris.Context) {
// destroy/removes the entire session and cookie
ctx.SessionDestroy()
ctx.Log("You have to refresh the page to completely remove the session (on browsers), so the name should NOT be empty NOW, is it?\n ame: %s\n\nAlso check your cookies in your browser's cookies, should be no field for localhost/127.0.0.1 (or whatever you use)", ctx.Session().GetString("name"))
- ctx.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")
@@ -738,7 +641,7 @@ func main() {
iris.Static("/js", "./static/js", 1)
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
@@ -915,21 +818,14 @@ You can read the full article [here](https://translate.google.com/translate?sl=a
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
------------
-Current: **v5.1.3**
+Current: **v6.0.0**
-Stable: **[v4 LTS](https://github.com/kataras/iris/tree/4.0.0#versioning)**
-
-
-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.
+Stable: **[v5/fasthttp](https://github.com/kataras/iris/tree/5.0.0)**
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).
-##### 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:
-- 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.
- 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.
@@ -983,7 +878,7 @@ License
------------
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
diff --git a/configuration.go b/configuration.go
index 391f4924..f99a5d90 100644
--- a/configuration.go
+++ b/configuration.go
@@ -1,7 +1,10 @@
package iris
import (
+ "crypto/tls"
"io"
+ "net"
+ "net/http"
"net/url"
"os"
"strconv"
@@ -10,7 +13,6 @@ import (
"github.com/imdario/mergo"
"github.com/kataras/go-options"
"github.com/kataras/go-sessions"
- "github.com/valyala/fasthttp"
)
type (
@@ -52,7 +54,7 @@ type Configuration struct {
// when calling the template helper '{{url }}'
// *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
//
// 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)
VScheme string
- // MaxRequestBodySize Maximum request body size.
- //
- // The server rejects requests with bodies exceeding this limit.
- //
- // By default request body size is 8MB.
- MaxRequestBodySize int
+ ReadTimeout time.Duration // maximum duration before timing out read of the request
+ WriteTimeout time.Duration // maximum duration before timing out write of the response
- // 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.
- ReadBufferSize int
+ // MaxHeaderBytes controls the maximum number of bytes the
+ // server will read parsing the request header's keys and
+ // values, including the request line. It does not limit the
+ // size of the request body.
+ // If zero, DefaultMaxHeaderBytes is used.
+ MaxHeaderBytes int
- // Per-connection buffer size for responses' writing.
- //
- // Default buffer size is used if not set.
- WriteBufferSize int
+ // TLSNextProto optionally specifies a function to take over
+ // ownership of the provided TLS connection when an NPN/ALPN
+ // protocol upgrade has occurred. The map key is the protocol
+ // 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).
- //
- // This also limits the maximum duration for idle keep-alive
- // connections.
- //
- // 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
+ // ConnState specifies an optional callback function that is
+ // called when a client connection changes state. See the
+ // ConnState type and associated constants for details.
+ ConnState func(net.Conn, http.ConnState)
// 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
@@ -231,7 +211,7 @@ var (
// when calling the template helper '{{url }}'
// *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
//
// Default comes from iris.Listen/.Serve with iris' listeners (iris.TCP4/UNIX/TLS/LETSENCRYPT)
@@ -253,80 +233,50 @@ var (
c.VScheme = val
}
}
-
- // 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.
+ // maximum duration before timing out read of the request
OptionReadTimeout = func(val time.Duration) OptionSet {
return func(c *Configuration) {
c.ReadTimeout = val
}
}
-
- // Maximum duration for writing the full response (including body).
- //
- // By default response write timeout is unlimited.
+ // maximum duration before timing out write of the response
OptionWriteTimeout = func(val time.Duration) OptionSet {
return func(c *Configuration) {
c.WriteTimeout = val
}
}
- // OptionMaxConnsPerIP Maximum number of concurrent client connections allowed per IP.
- //
- // By default unlimited number of concurrent connections
- // may be established to the server from a single IP address.
- OptionMaxConnsPerIP = func(val int) OptionSet {
+ // MaxHeaderBytes controls the maximum number of bytes the
+ // server will read parsing the request header's keys and
+ // values, including the request line. It does not limit the
+ // size of the request body.
+ // If zero, DefaultMaxHeaderBytes(8MB) is used.
+ OptionMaxHeaderBytes = func(val int) OptionSet {
return func(c *Configuration) {
- c.MaxConnsPerIP = val
+ c.MaxHeaderBytes = val
}
}
- // OptionMaxRequestsPerConn 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.
- OptionMaxRequestsPerConn = func(val int) OptionSet {
+ // TLSNextProto optionally specifies a function to take over
+ // ownership of the provided TLS connection when an NPN/ALPN
+ // protocol upgrade has occurred. The map key is the protocol
+ // 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.
+ OptionTLSNextProto = func(val map[string]func(*http.Server, *tls.Conn, http.Handler)) OptionSet {
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"
// StaticCacheDuration expiration duration for INACTIVE file handlers, it's a global configuration field to all iris instances
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
@@ -493,9 +438,6 @@ const (
DefaultDisablePathEscape = false
DefaultCharset = "UTF-8"
DefaultLoggerPreffix = "[IRIS] "
- // DefaultMaxRequestBodySize is 8MB
- DefaultMaxRequestBodySize = 2 * fasthttp.DefaultMaxRequestBodySize
-
// Per-connection buffer size for requests' reading.
// This also limits the maximum header size.
//
@@ -503,19 +445,17 @@ const (
// and/or multi-KB headers (for example, BIG cookies).
//
// Default buffer size is 8MB
- DefaultReadBufferSize = 8096
+ DefaultMaxHeaderBytes = 8096
- // Per-connection buffer size for responses' writing.
- //
- // Default buffer size is 8MB
- DefaultWriteBufferSize = 8096
+ // DefaultReadTimeout no read client timeout
+ DefaultReadTimeout = 0
+ // DefaultWriteTimeout no serve client timeout
+ DefaultWriteTimeout = 0
)
var (
// DefaultLoggerOut is the default logger's output
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
@@ -523,11 +463,9 @@ func DefaultConfiguration() Configuration {
return Configuration{
VHost: "",
VScheme: "",
- MaxRequestBodySize: DefaultMaxRequestBodySize,
- ReadBufferSize: DefaultReadBufferSize,
- WriteBufferSize: DefaultWriteBufferSize,
- MaxConnsPerIP: 0,
- MaxRequestsPerConn: 0,
+ ReadTimeout: DefaultReadTimeout,
+ WriteTimeout: DefaultWriteTimeout,
+ MaxHeaderBytes: DefaultMaxHeaderBytes,
CheckForUpdates: false,
CheckForUpdatesSync: false,
DisablePathCorrection: DefaultDisablePathCorrection,
@@ -675,14 +613,14 @@ type WebsocketConfiguration struct {
// Error specifies the function for generating HTTP error responses.
//
// The default behavior is to store the reason in the context (ctx.Set(reason)) and fire any custom error (ctx.EmitError(status))
- Error func(ctx *Context, status int, reason string)
+ Error func(ctx *Context, status int, reason error)
// CheckOrigin returns true if the request Origin header is acceptable. If
// CheckOrigin is nil, the host in the Origin header must not be set or
// must match the host of the request.
//
// The default behavior is to allow all origins
// you can change this behavior by setting the iris.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin
- CheckOrigin func(ctx *Context) bool
+ CheckOrigin func(r *http.Request) bool
}
var (
@@ -748,7 +686,7 @@ var (
}
}
// 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) {
c.Websocket.Error = val
}
@@ -756,7 +694,7 @@ var (
// 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
// 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) {
c.Websocket.CheckOrigin = val
}
@@ -764,30 +702,30 @@ var (
)
const (
- // DefaultWriteTimeout 15 * time.Second
- DefaultWriteTimeout = 15 * time.Second
- // DefaultPongTimeout 60 * time.Second
- DefaultPongTimeout = 60 * time.Second
- // DefaultPingPeriod (DefaultPongTimeout * 9) / 10
- DefaultPingPeriod = (DefaultPongTimeout * 9) / 10
- // DefaultMaxMessageSize 1024
- DefaultMaxMessageSize = 1024
+ // DefaultWebsocketWriteTimeout 15 * time.Second
+ DefaultWebsocketWriteTimeout = 15 * time.Second
+ // DefaultWebsocketPongTimeout 60 * time.Second
+ DefaultWebsocketPongTimeout = 60 * time.Second
+ // DefaultWebsocketPingPeriod (DefaultPongTimeout * 9) / 10
+ DefaultWebsocketPingPeriod = (DefaultWebsocketPongTimeout * 9) / 10
+ // DefaultWebsocketMaxMessageSize 1024
+ DefaultWebsocketMaxMessageSize = 1024
)
var (
// 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.EmitError(status)
}
// DefaultWebsocketCheckOrigin is the default method to allow websocket clients to connect to this server
// you can change this behavior by setting the iris.Config.Websocket.CheckOrigin = iris.WebsocketCheckSameOrigin
- DefaultWebsocketCheckOrigin = func(ctx *Context) bool {
+ DefaultWebsocketCheckOrigin = func(r *http.Request) bool {
return true
}
// WebsocketCheckSameOrigin returns true if the origin is not set or is equal to the request host
- WebsocketCheckSameOrigin = func(ctx *Context) bool {
- origin := ctx.RequestHeader("origin")
+ WebsocketCheckSameOrigin = func(r *http.Request) bool {
+ origin := r.Header.Get("origin")
if len(origin) == 0 {
return true
}
@@ -795,17 +733,17 @@ var (
if err != nil {
return false
}
- return u.Host == ctx.HostString()
+ return u.Host == r.Host
}
)
// DefaultWebsocketConfiguration returns the default config for iris-ws websocket package
func DefaultWebsocketConfiguration() WebsocketConfiguration {
return WebsocketConfiguration{
- WriteTimeout: DefaultWriteTimeout,
- PongTimeout: DefaultPongTimeout,
- PingPeriod: DefaultPingPeriod,
- MaxMessageSize: DefaultMaxMessageSize,
+ WriteTimeout: DefaultWebsocketWriteTimeout,
+ PongTimeout: DefaultWebsocketPongTimeout,
+ PingPeriod: DefaultWebsocketPingPeriod,
+ MaxMessageSize: DefaultWebsocketMaxMessageSize,
BinaryMessages: false,
ReadBufferSize: 4096,
WriteBufferSize: 4096,
diff --git a/configuration_test.go b/configuration_test.go
index 5b613802..9cb3e82f 100644
--- a/configuration_test.go
+++ b/configuration_test.go
@@ -2,9 +2,10 @@
package iris_test
import (
- "github.com/kataras/iris"
"reflect"
"testing"
+
+ "github.com/kataras/iris"
)
// go test -v -run TestConfig*
diff --git a/context.go b/context.go
index 78a8e849..a35083c0 100644
--- a/context.go
+++ b/context.go
@@ -1,14 +1,15 @@
package iris
import (
- "bufio"
"bytes"
- "encoding/base64"
"encoding/json"
"encoding/xml"
"fmt"
"io"
+ "io/ioutil"
+ "mime/multipart"
"net"
+ "net/http"
"os"
"path"
"reflect"
@@ -16,14 +17,12 @@ import (
"runtime"
"strconv"
"strings"
- "sync"
"time"
"github.com/iris-contrib/formBinder"
"github.com/kataras/go-errors"
"github.com/kataras/go-fs"
"github.com/kataras/go-sessions"
- "github.com/valyala/fasthttp"
)
const (
@@ -67,11 +66,6 @@ const (
// stopExecutionPosition used inside the Context, is the number which shows us that the context's middleware manualy stop the execution
stopExecutionPosition = 255
- // used inside GetFlash to store the lifetime request flash messages
- flashMessagesStoreContextKey = "_iris_flash_messages_"
- flashMessageCookiePrefix = "_iris_flash_message_"
- cookieHeaderID = "Cookie: "
- cookieHeaderIDLen = len(cookieHeaderID)
)
// errors
@@ -83,16 +77,70 @@ var (
errServeContent = errors.New("While trying to serve content to the client. Trace %s")
)
+type (
+ requestValue struct {
+ key []byte
+ value interface{}
+ }
+ requestValues []requestValue
+)
+
+func (r *requestValues) Set(key string, value interface{}) {
+ args := *r
+ n := len(args)
+ for i := 0; i < n; i++ {
+ kv := &args[i]
+ if string(kv.key) == key {
+ kv.value = value
+ return
+ }
+ }
+
+ c := cap(args)
+ if c > n {
+ args = args[:n+1]
+ kv := &args[n]
+ kv.key = append(kv.key[:0], key...)
+ kv.value = value
+ *r = args
+ return
+ }
+
+ kv := requestValue{}
+ kv.key = append(kv.key[:0], key...)
+ kv.value = value
+ *r = append(args, kv)
+}
+
+func (r *requestValues) Get(key string) interface{} {
+ args := *r
+ n := len(args)
+ for i := 0; i < n; i++ {
+ kv := &args[i]
+ if string(kv.key) == key {
+ return kv.value
+ }
+ }
+ return nil
+}
+
+func (r *requestValues) Reset() {
+ *r = (*r)[:0]
+}
+
type (
// Map is just a conversion for a map[string]interface{}
// should not be used inside Render when PongoEngine is used.
Map map[string]interface{}
+
// Context is resetting every time a request is coming to the server
// it is not good practice to use this object in goroutines, for these cases use the .Clone()
Context struct {
- *fasthttp.RequestCtx
- framework *Framework
+ ResponseWriter *ResponseWriter
+ Request *http.Request
+ values requestValues
+ framework *Framework
//keep track all registed middleware (handlers)
Middleware Middleware // exported because is useful for debugging
session sessions.Session
@@ -101,10 +149,11 @@ type (
}
)
-// GetRequestCtx returns the current fasthttp context
-func (ctx *Context) GetRequestCtx() *fasthttp.RequestCtx {
- return ctx.RequestCtx
-}
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// -----------------------------Handler(s) Execution------------------------------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
// Do calls the first handler only, it's like Next with negative pos, used only on Router&MemoryRouter
func (ctx *Context) Do() {
@@ -116,12 +165,10 @@ func (ctx *Context) Do() {
func (ctx *Context) Next() {
//set position to the next
ctx.Pos++
- midLen := len(ctx.Middleware)
//run the next
- if ctx.Pos < midLen {
+ if ctx.Pos < len(ctx.Middleware) {
ctx.Middleware[ctx.Pos].Serve(ctx)
}
-
}
// StopExecution just sets the .pos to 255 in order to not move to the next middlewares(if any)
@@ -129,8 +176,6 @@ func (ctx *Context) StopExecution() {
ctx.Pos = stopExecutionPosition
}
-//
-
// IsStopped checks and returns true if the current position of the Context is 255, means that the StopExecution has called
func (ctx *Context) IsStopped() bool {
return ctx.Pos == stopExecutionPosition
@@ -141,45 +186,41 @@ func (ctx *Context) GetHandlerName() string {
return runtime.FuncForPC(reflect.ValueOf(ctx.Middleware[len(ctx.Middleware)-1]).Pointer()).Name()
}
-/* Request */
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// -----------------------------Request URL, Method, IP & Headers getters---------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
-// URLParam returns the get parameter from a request , if any
-func (ctx *Context) URLParam(key string) string {
- return string(ctx.RequestCtx.Request.URI().QueryArgs().Peek(key))
+// Method returns the http request method
+// same as *http.Request.Method
+func (ctx *Context) Method() string {
+ return ctx.Request.Method
}
-// URLParams returns a map of a list of each url(query) parameter
-func (ctx *Context) URLParams() map[string]string {
- urlparams := make(map[string]string)
- ctx.RequestCtx.Request.URI().QueryArgs().VisitAll(func(key, value []byte) {
- urlparams[string(key)] = string(value)
- })
- return urlparams
+// Host returns the host part of the current url
+func (ctx *Context) Host() string {
+ return ctx.Request.URL.Host
}
-// URLParamInt returns the url query parameter as int value from a request , returns error on parse fail
-func (ctx *Context) URLParamInt(key string) (int, error) {
- return strconv.Atoi(ctx.URLParam(key))
+// ServerHost returns the server host taken by *http.Request.Host
+func (ctx *Context) ServerHost() string {
+ return ctx.Request.Host
}
-// URLParamInt64 returns the url query parameter as int64 value from a request , returns error on parse fail
-func (ctx *Context) URLParamInt64(key string) (int64, error) {
- return strconv.ParseInt(ctx.URLParam(key), 10, 64)
-}
+// Subdomain returns the subdomain (string) of this request, if any
+func (ctx *Context) Subdomain() (subdomain string) {
+ host := ctx.Host()
+ if index := strings.IndexByte(host, '.'); index > 0 {
+ subdomain = host[0:index]
+ }
-// MethodString returns the HTTP Method
-func (ctx *Context) MethodString() string {
- return string(ctx.Method())
-}
-
-// HostString returns the Host of the request( the url as string )
-func (ctx *Context) HostString() string {
- return string(ctx.Host())
+ return
}
// VirtualHostname returns the hostname that user registers, host path maybe differs from the real which is HostString, which taken from a net.listener
func (ctx *Context) VirtualHostname() string {
- realhost := ctx.HostString()
+ realhost := ctx.Host()
hostname := realhost
virtualhost := ctx.framework.mux.hostname
@@ -203,37 +244,28 @@ func (ctx *Context) VirtualHostname() string {
return hostname
}
-// PathString returns the full escaped path as string
+// Path returns the full escaped path as string
// for unescaped use: ctx.RequestCtx.RequestURI() or RequestPath(escape bool)
-func (ctx *Context) PathString() string {
+func (ctx *Context) Path() string {
return ctx.RequestPath(!ctx.framework.Config.DisablePathEscape)
}
// RequestPath returns the requested path
func (ctx *Context) RequestPath(escape bool) string {
if escape {
- // return utils.BytesToString(ctx.RequestCtx.Path())
- return string(ctx.RequestCtx.URI().PathOriginal())
+ return ctx.Request.URL.EscapedPath()
}
- return string(ctx.RequestCtx.RequestURI())
+ return ctx.Request.RequestURI
}
-// RequestIP gets just the Remote Address from the client.
-func (ctx *Context) RequestIP() string {
- if ip, _, err := net.SplitHostPort(strings.TrimSpace(ctx.RequestCtx.RemoteAddr().String())); err == nil {
- return ip
- }
- return ""
-}
-
-// RemoteAddr is like RequestIP but it checks for proxy servers also, tries to get the real client's request IP
+// RemoteAddr tries to return the real client's request IP
func (ctx *Context) RemoteAddr() string {
- header := string(ctx.RequestCtx.Request.Header.Peek("X-Real-Ip"))
+ header := ctx.RequestHeader("X-Real-Ip")
realIP := strings.TrimSpace(header)
if realIP != "" {
return realIP
}
- realIP = string(ctx.RequestCtx.Request.Header.Peek("X-Forwarded-For"))
+ realIP = ctx.RequestHeader("X-Forwarded-For")
idx := strings.IndexByte(realIP, ',')
if idx >= 0 {
realIP = realIP[0:idx]
@@ -242,15 +274,22 @@ func (ctx *Context) RemoteAddr() string {
if realIP != "" {
return realIP
}
- return ctx.RequestIP()
-
+ addr := strings.TrimSpace(ctx.Request.RemoteAddr)
+ if len(addr) == 0 {
+ return ""
+ }
+ // if addr has port use the net.SplitHostPort otherwise(error occurs) take as it is
+ if ip, _, err := net.SplitHostPort(addr); err == nil {
+ return ip
+ }
+ return addr
}
// RequestHeader returns the request header's value
// accepts one parameter, the key of the header (string)
// returns string
func (ctx *Context) RequestHeader(k string) string {
- return string(ctx.RequestCtx.Request.Header.Peek(k))
+ return ctx.Request.Header.Get(k)
}
// IsAjax returns true if this request is an 'ajax request'( XMLHttpRequest)
@@ -260,87 +299,93 @@ func (ctx *Context) IsAjax() bool {
return ctx.RequestHeader("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
}
-// FormValueString returns a single value, as string, from post request's data
-func (ctx *Context) FormValueString(name string) string {
- return string(ctx.FormValue(name))
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// -----------------------------GET & POST arguments------------------------------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+
+// URLParam returns the get parameter from a request , if any
+func (ctx *Context) URLParam(key string) string {
+ return ctx.Request.URL.Query().Get(key)
}
-// FormValues returns a slice of string from post request's data
-func (ctx *Context) FormValues(name string) []string {
- arrBytes := ctx.PostArgs().PeekMulti(name)
- arrStr := make([]string, len(arrBytes))
- for i, v := range arrBytes {
- arrStr[i] = string(v)
- }
- return arrStr
-}
+// URLParams returns a map of GET query parameters seperated by comma if more than one
+// it returns an empty map if nothing founds
+func (ctx *Context) URLParams() map[string]string {
+ values := map[string]string{}
-// PostValuesAll returns all post data values with their keys
-// multipart, form data, get & post query arguments
-//
-// NOTE: A check for nil is necessary for zero results
-func (ctx *Context) PostValuesAll() map[string][]string {
- // first check if we have multipart form
- multipartForm, err := ctx.MultipartForm()
- if err == nil {
- //we have multipart form
- return multipartForm.Value
- }
-
- postArgs := ctx.PostArgs()
- queryArgs := ctx.QueryArgs()
-
- len := postArgs.Len() + queryArgs.Len()
- if len == 0 {
- return nil // nothing found
- }
-
- valuesAll := make(map[string][]string, len)
-
- visitor := func(k []byte, v []byte) {
- key := string(k)
- value := string(v)
- // for slices
- if valuesAll[key] != nil {
- valuesAll[key] = append(valuesAll[key], value)
- } else {
- valuesAll[key] = []string{value}
+ q := ctx.URLParamsAsMulti()
+ if q != nil {
+ for k, v := range q {
+ values[k] = strings.Join(v, ",")
}
}
- postArgs.VisitAll(visitor)
- queryArgs.VisitAll(visitor)
- return valuesAll
-}
-
-// PostValues returns the post data values as []string of a single key/name
-func (ctx *Context) PostValues(name string) []string {
- var values []string
- if v := ctx.PostValuesAll(); v != nil && len(v) > 0 {
- values = v[name]
- }
return values
}
-// PostValue returns the post data value of a single key/name
-// returns an empty string if nothing found
+// URLParamsAsMulti returns a map of list contains the url get parameters
+func (ctx *Context) URLParamsAsMulti() map[string][]string {
+ return ctx.Request.URL.Query()
+}
+
+// URLParamInt returns the url query parameter as int value from a request , returns error on parse fail
+func (ctx *Context) URLParamInt(key string) (int, error) {
+ return strconv.Atoi(ctx.URLParam(key))
+}
+
+// URLParamInt64 returns the url query parameter as int64 value from a request , returns error on parse fail
+func (ctx *Context) URLParamInt64(key string) (int64, error) {
+ return strconv.ParseInt(ctx.URLParam(key), 10, 64)
+}
+
+func (ctx *Context) askParseForm() error {
+ if ctx.Request.Form == nil {
+ if err := ctx.Request.ParseForm(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// FormValues returns all post data values with their keys
+// form data, get, post & put query arguments
+//
+// NOTE: A check for nil is necessary for zero results
+func (ctx *Context) FormValues() map[string][]string {
+ // we skip the check of multipart form, takes too much memory, if user wants it can do manually now.
+ if err := ctx.askParseForm(); err != nil {
+ return nil
+ }
+ return ctx.Request.Form // nothing more to do, it's already contains both query and post & put args.
+}
+
+// FormValue returns a single form value by its name/key
+func (ctx *Context) FormValue(name string) string {
+ return ctx.Request.FormValue(name)
+}
+
+// PostValue returns a form's only-post value by its name
+// same as Request.PostFormValue
func (ctx *Context) PostValue(name string) string {
- if v := ctx.PostValues(name); len(v) > 0 {
- return v[0]
- }
- return ""
+ return ctx.Request.PostFormValue(name)
}
-// Subdomain returns the subdomain (string) of this request, if any
-func (ctx *Context) Subdomain() (subdomain string) {
- host := ctx.HostString()
- if index := strings.IndexByte(host, '.'); index > 0 {
- subdomain = host[0:index]
- }
-
- return
+// FormFile returns the first file for the provided form key.
+// FormFile calls ctx.Request.ParseMultipartForm and ParseForm if necessary.
+//
+// same as Request.FormFile
+func (ctx *Context) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
+ return ctx.Request.FormFile(key)
}
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// -----------------------------Request Body Binders/Readers----------------------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+
// BodyDecoder is an interface which any struct can implement in order to customize the decode action
// from ReadJSON and ReadXML
//
@@ -381,7 +426,14 @@ func (u UnmarshalerFunc) Unmarshal(data []byte, v interface{}) error {
// UnmarshalBody reads the request's body and binds it to a value or pointer of any type
// Examples of usage: context.ReadJSON, context.ReadXML
func (ctx *Context) UnmarshalBody(v interface{}, unmarshaler Unmarshaler) error {
- rawData := ctx.Request.Body()
+ if ctx.Request.Body == nil {
+ return errors.New("Empty body, please send request body!")
+ }
+
+ rawData, err := ioutil.ReadAll(ctx.Request.Body)
+ if err != nil {
+ return err
+ }
// check if the v contains its own decode
// in this case the v should be a pointer also,
@@ -414,22 +466,35 @@ func (ctx *Context) ReadXML(xmlObject interface{}) error {
// ReadForm binds the formObject with the form data
// it supports any kind of struct
func (ctx *Context) ReadForm(formObject interface{}) error {
- return errReadBody.With(formBinder.Decode(ctx.PostValuesAll(), formObject))
+ values := ctx.FormValues()
+ if values == nil {
+ return errors.New("An empty form passed on context.ReadForm")
+ }
+ return errReadBody.With(formBinder.Decode(values, formObject))
+}
+
+// ResetBody resets the body of the response
+func (ctx *Context) ResetBody() {
+ ctx.ResponseWriter.ResetBody()
}
/* Response */
// SetContentType sets the response writer's header key 'Content-Type' to a given value(s)
func (ctx *Context) SetContentType(s string) {
- ctx.RequestCtx.Response.Header.Set(contentType, s)
+ ctx.ResponseWriter.Header().Set(contentType, s)
}
-// SetHeader write to the response writer's header to a given key the given value(s)
-//
-// Note: If you want to send a multi-line string as header's value use: strings.TrimSpace first.
+// SetHeader write to the response writer's header to a given key the given value
func (ctx *Context) SetHeader(k string, v string) {
- //v = strings.TrimSpace(v)
- ctx.RequestCtx.Response.Header.Set(k, v)
+ ctx.ResponseWriter.Header().Add(k, v)
+}
+
+// SetStatusCode sets the status code header to the response
+//
+// NOTE: Iris takes cares of multiple header writing
+func (ctx *Context) SetStatusCode(statusCode int) {
+ ctx.ResponseWriter.WriteHeader(statusCode)
}
// it used only inside Redirect,
@@ -449,17 +514,12 @@ func (ctx *Context) Redirect(urlToRedirect string, statusHeader ...int) {
httpStatus = statusHeader[0]
}
- // #355
- if ctx.IsTLS() {
- u := ctx.URI()
- u.SetSchemeBytes(httpsSchemeOnlyBytes)
- u.Update(urlToRedirect)
- ctx.SetHeader("Location", string(u.FullURI()))
- ctx.SetStatusCode(httpStatus)
- return
+ if urlToRedirect == ctx.Path() {
+ if ctx.framework.Config.IsDevelopment {
+ ctx.Log("Trying to redirect to itself. FROM: %s TO: %s", ctx.Path(), urlToRedirect)
+ }
}
-
- ctx.RequestCtx.Redirect(urlToRedirect, httpStatus)
+ http.Redirect(ctx.ResponseWriter.ResponseWriter, ctx.Request, urlToRedirect, httpStatus)
}
// RedirectTo does the same thing as Redirect but instead of receiving a uri or path it receives a route name
@@ -470,6 +530,12 @@ func (ctx *Context) RedirectTo(routeName string, args ...interface{}) {
}
}
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// -----------------------------(Custom) Errors-----------------------------------------
+// ----------------------Look iris.OnError/EmitError for more---------------------------
+// -------------------------------------------------------------------------------------
+
// NotFound emits an error 404 to the client, using the custom http errors
// if no custom errors provided then it sends the default error message
func (ctx *Context) NotFound() {
@@ -488,12 +554,48 @@ func (ctx *Context) EmitError(statusCode int) {
ctx.StopExecution()
}
-// Write writes a string to the client, something like fmt.Printf but for the web
-func (ctx *Context) Write(format string, a ...interface{}) {
- //this doesn't work with gzip, so just write the []byte better |ctx.ResponseWriter.WriteString(fmt.Sprintf(format, a...))
- ctx.RequestCtx.WriteString(fmt.Sprintf(format, a...))
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// -----------------------------Raw write methods---------------------------------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+
+// Write writes the contents to the response writer.
+//
+// Returns the number of bytes written and any write error encountered
+func (ctx *Context) Write(contents []byte) (n int, err error) {
+ return ctx.ResponseWriter.Write(contents)
}
+// Writef formats according to a format specifier and writes to the response.
+//
+// Returns the number of bytes written and any write error encountered
+func (ctx *Context) Writef(format string, a ...interface{}) (n int, err error) {
+ return fmt.Fprintf(ctx.ResponseWriter, format, a...)
+}
+
+// WriteString writes a simple string to the response.
+//
+// Returns the number of bytes written and any write error encountered
+func (ctx *Context) WriteString(s string) (n int, err error) {
+ return io.WriteString(ctx.ResponseWriter, s)
+}
+
+// SetBodyString writes a simple string to the response.
+func (ctx *Context) SetBodyString(s string) {
+ ctx.ResponseWriter.SetBodyString(s)
+}
+
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// -------------------------Context's gzip inline response writer ----------------------
+// ---------------------Look template.go & iris.go for more options---------------------
+// -------------------------------------------------------------------------------------
+
+var (
+ errClientDoesNotSupportGzip = errors.New("Client doesn't supports gzip compression")
+)
+
func (ctx *Context) clientAllowsGzip() bool {
if h := ctx.RequestHeader(acceptEncodingHeader); h != "" {
for _, v := range strings.Split(h, ";") {
@@ -502,22 +604,47 @@ func (ctx *Context) clientAllowsGzip() bool {
}
}
}
-
return false
}
-// Gzip accepts bytes, which are compressed to gzip format and sent to the client
-func (ctx *Context) Gzip(b []byte, status int) {
- ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
-
+// WriteGzip accepts bytes, which are compressed to gzip format and sent to the client.
+// returns the number of bytes written and an error ( if the client doesn' supports gzip compression)
+func (ctx *Context) WriteGzip(b []byte) (int, error) {
if ctx.clientAllowsGzip() {
- _, err := fasthttp.WriteGzip(ctx.RequestCtx.Response.BodyWriter(), b)
+ ctx.ResponseWriter.Header().Add(varyHeader, acceptEncodingHeader)
+
+ gzipWriter := fs.AcquireGzipWriter(ctx.ResponseWriter)
+ n, err := gzipWriter.Write(b)
+ fs.ReleaseGzipWriter(gzipWriter)
+
if err == nil {
ctx.SetHeader(contentEncodingHeader, "gzip")
+ } // else write the contents as it is? no let's create a new func for this
+ return n, err
+ }
+ return 0, errClientDoesNotSupportGzip
+}
+
+// TryWriteGzip accepts bytes, which are compressed to gzip format and sent to the client.
+// If client does not supprots gzip then the contents are written as they are, uncompressed.
+func (ctx *Context) TryWriteGzip(b []byte) (int, error) {
+ n, err := ctx.WriteGzip(b)
+ if err != nil {
+ // check if the error came from gzip not allowed and not the writer itself
+ if _, ok := err.(*errors.Error); ok {
+ // client didn't supported gzip, write them uncompressed:
+ return ctx.ResponseWriter.Write(b)
}
}
+ return n, err
}
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// -----------------------------Render and powerful content negotiation-----------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+
// renderSerialized renders contents with a serializer with status OK which you can change using RenderWithStatus or ctx.SetStatusCode(iris.StatusCode)
func (ctx *Context) renderSerialized(contentType string, obj interface{}, options ...map[string]interface{}) error {
s := ctx.framework.serializers
@@ -541,20 +668,12 @@ func (ctx *Context) renderSerialized(contentType string, obj interface{}, option
ctype += "; charset=" + charset
}
ctx.SetContentType(ctype)
-
- if gzipEnabled && ctx.clientAllowsGzip() {
- _, err := fasthttp.WriteGzip(ctx.RequestCtx.Response.BodyWriter(), finalResult)
- if err != nil {
- return err
- }
- ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
- ctx.SetHeader(contentEncodingHeader, "gzip")
+ if gzipEnabled {
+ ctx.TryWriteGzip(finalResult)
} else {
- ctx.Response.SetBody(finalResult)
+ ctx.ResponseWriter.Write(finalResult)
}
-
ctx.SetStatusCode(StatusOK)
-
return nil
}
@@ -588,7 +707,7 @@ func (ctx *Context) RenderWithStatus(status int, name string, binding interface{
// builds up the response from the specified template or a serialize engine.
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engine
func (ctx *Context) Render(name string, binding interface{}, options ...map[string]interface{}) error {
- errCode := ctx.RequestCtx.Response.StatusCode()
+ errCode := ctx.ResponseWriter.StatusCode()
if errCode <= 0 {
errCode = StatusOK
}
@@ -599,9 +718,9 @@ func (ctx *Context) Render(name string, binding interface{}, options ...map[stri
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engine
func (ctx *Context) MustRender(name string, binding interface{}, options ...map[string]interface{}) {
if err := ctx.Render(name, binding, options...); err != nil {
- ctx.HTML(StatusServiceUnavailable, fmt.Sprintf("Template: %s\nIP: %s
%s", name, ctx.RemoteAddr(), err.Error()))
+ ctx.HTML(StatusServiceUnavailable, fmt.Sprintf("Template: %s
%s", name, err.Error()))
if ctx.framework.Config.IsDevelopment {
- ctx.framework.Logger.Printf("MustRender panics for client with IP: %s On template: %s.Trace: %s\n", ctx.RemoteAddr(), name, err)
+ ctx.framework.Logger.Printf("MustRender panics on template: %s.Trace: %s\n", name, err)
}
}
}
@@ -617,8 +736,8 @@ func (ctx *Context) HTML(status int, htmlContents string) {
if err := ctx.RenderWithStatus(status, contentHTML, htmlContents); err != nil {
// if no serialize engine found for text/html
ctx.SetContentType(contentHTML + "; charset=" + ctx.framework.Config.Charset)
- ctx.RequestCtx.SetStatusCode(status)
- ctx.RequestCtx.WriteString(htmlContents)
+ ctx.SetStatusCode(status)
+ ctx.WriteString(htmlContents)
}
}
@@ -660,12 +779,18 @@ func (ctx *Context) Markdown(status int, markdown string) {
ctx.HTML(status, ctx.MarkdownString(markdown))
}
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// --------------------Static content serve by context implementation-------------------
+// --------------------Look iris.go for more useful Static web system methods-----------
+// -------------------------------------------------------------------------------------
+
// staticCachePassed checks the IfModifiedSince header and
// returns true if (client-side) duration has expired
func (ctx *Context) staticCachePassed(modtime time.Time) bool {
if t, err := time.Parse(ctx.framework.Config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && 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)
return true
}
@@ -681,11 +806,11 @@ func (ctx *Context) SetClientCachedBody(status int, bodyContent []byte, cType st
modtimeFormatted := modtime.UTC().Format(ctx.framework.Config.TimeFormat)
- ctx.Response.Header.Set(contentType, cType)
- ctx.Response.Header.Set(lastModified, modtimeFormatted)
+ ctx.ResponseWriter.Header().Set(contentType, cType)
+ ctx.ResponseWriter.Header().Set(lastModified, modtimeFormatted)
ctx.SetStatusCode(status)
- ctx.Response.SetBody(bodyContent)
+ ctx.ResponseWriter.Write(bodyContent)
}
// ServeContent serves content, headers are autoset
@@ -695,25 +820,25 @@ func (ctx *Context) SetClientCachedBody(status int, bodyContent []byte, cType st
// Doesn't implements resuming (by range), use ctx.SendFile instead
func (ctx *Context) ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error {
if t, err := time.Parse(ctx.framework.Config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && modtime.Before(t.Add(1*time.Second)) {
- ctx.RequestCtx.Response.Header.Del(contentType)
- ctx.RequestCtx.Response.Header.Del(contentLength)
- ctx.RequestCtx.SetStatusCode(StatusNotModified)
+ ctx.ResponseWriter.Header().Del(contentType)
+ ctx.ResponseWriter.Header().Del(contentLength)
+ ctx.SetStatusCode(StatusNotModified)
return nil
}
- ctx.RequestCtx.Response.Header.Set(contentType, fs.TypeByExtension(filename))
- ctx.RequestCtx.Response.Header.Set(lastModified, modtime.UTC().Format(ctx.framework.Config.TimeFormat))
- ctx.RequestCtx.SetStatusCode(StatusOK)
+ ctx.ResponseWriter.Header().Set(contentType, fs.TypeByExtension(filename))
+ ctx.ResponseWriter.Header().Set(lastModified, modtime.UTC().Format(ctx.framework.Config.TimeFormat))
+ ctx.SetStatusCode(StatusOK)
var out io.Writer
if gzipCompression && ctx.clientAllowsGzip() {
- ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
+ ctx.ResponseWriter.Header().Add(varyHeader, acceptEncodingHeader)
ctx.SetHeader(contentEncodingHeader, "gzip")
- gzipWriter := fs.AcquireGzipWriter(ctx.RequestCtx.Response.BodyWriter())
+ gzipWriter := fs.AcquireGzipWriter(ctx.ResponseWriter)
defer fs.ReleaseGzipWriter(gzipWriter)
out = gzipWriter
} else {
- out = ctx.RequestCtx.Response.BodyWriter()
+ out = ctx.ResponseWriter
}
_, err := io.Copy(out, content)
return errServeContent.With(err)
@@ -725,7 +850,7 @@ func (ctx *Context) ServeContent(content io.ReadSeeker, filename string, modtime
// gzipCompression (bool)
//
// You can define your own "Content-Type" header also, after this function call
-// This function doesn't implement resuming (by range), use ctx.SendFile/fasthttp.ServeFileUncompressed(ctx.RequestCtx,path)/fasthttpServeFile(ctx.RequestCtx,path) instead
+// This function doesn't implement resuming (by range), use ctx.SendFile instead
//
// Use it when you want to serve css/js/... files to the client, for bigger files and 'force-download' use the SendFile
func (ctx *Context) ServeFile(filename string, gzipCompression bool) error {
@@ -750,60 +875,25 @@ func (ctx *Context) ServeFile(filename string, gzipCompression bool) error {
//
// Use this instead of ServeFile to 'force-download' bigger files to the client
func (ctx *Context) SendFile(filename string, destinationName string) {
- ctx.RequestCtx.SendFile(filename)
- ctx.RequestCtx.Response.Header.Set(contentDisposition, "attachment;filename="+destinationName)
+ ctx.ServeFile(filename, false)
+ ctx.ResponseWriter.Header().Set(contentDisposition, "attachment;filename="+destinationName)
}
-// Stream same as StreamWriter
-func (ctx *Context) Stream(cb func(writer *bufio.Writer)) {
- ctx.StreamWriter(cb)
-}
-
-// StreamWriter registers the given stream writer for populating
-// response body.
-//
-//
-// This function may be used in the following cases:
-//
-// * if response body is too big (more than 10MB).
-// * if response body is streamed from slow external sources.
-// * if response body must be streamed to the client in chunks.
-// (aka `http server push`).
-//
-// See also the StreamReader
-func (ctx *Context) StreamWriter(cb func(writer *bufio.Writer)) {
- ctx.RequestCtx.SetBodyStreamWriter(cb)
-}
-
-// StreamReader sets response body stream and, optionally body size.
-//
-// If bodySize is >= 0, then the bodyStream must provide exactly bodySize bytes
-// before returning io.EOF.
-//
-// If bodySize < 0, then bodyStream is read until io.EOF.
-//
-// bodyStream.Close() is called after finishing reading all body data
-// if it implements io.Closer.
-//
-// See also the StreamWriter
-func (ctx *Context) StreamReader(bodyStream io.Reader, bodySize int) {
- ctx.RequestCtx.Response.SetBodyStream(bodyStream, bodySize)
-}
-
-/* Storage */
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// --------------------------------Storage----------------------------------------------
+// -----------------------User Values & Path parameters--------------------------------
+// -------------------------------------------------------------------------------------
// ValuesLen returns the total length of the user values storage, some of them maybe path parameters
func (ctx *Context) ValuesLen() (n int) {
- ctx.VisitUserValues(func([]byte, interface{}) {
- n++
- })
- return
+ return len(ctx.values)
}
// Get returns the user's value from a key
// if doesn't exists returns nil
func (ctx *Context) Get(key string) interface{} {
- return ctx.RequestCtx.UserValue(key)
+ return ctx.values.Get(key)
}
// GetFmt returns a value which has this format: func(format string, args ...interface{}) string
@@ -843,12 +933,23 @@ func (ctx *Context) GetInt(key string) (int, error) {
// Set sets a value to a key in the values map
func (ctx *Context) Set(key string, value interface{}) {
- ctx.RequestCtx.SetUserValue(key, value)
+ ctx.values.Set(key, value)
+}
+
+// VisitValues calls visitor for each existing context's temp values.
+//
+// visitor must not retain references to key and value after returning.
+// Make key and/or value copies if you need storing them after returning.
+func (ctx *Context) VisitValues(visitor func([]byte, interface{})) {
+ for i, n := 0, len(ctx.values); i < n; i++ {
+ kv := &ctx.values[i]
+ visitor(kv.key, kv.value)
+ }
}
// ParamsLen tries to return all the stored values which values are string, probably most of them will be the path parameters
func (ctx *Context) ParamsLen() (n int) {
- ctx.VisitUserValues(func(kb []byte, vg interface{}) {
+ ctx.VisitValues(func(kb []byte, vg interface{}) {
if _, ok := vg.(string); ok {
n++
}
@@ -863,6 +964,11 @@ func (ctx *Context) Param(key string) string {
return ctx.GetString(key)
}
+// ParamDecoded returns a url-query-decoded path parameter's value
+func (ctx *Context) ParamDecoded(key string) string {
+ return DecodeQuery(DecodeQuery(ctx.Param(key)))
+}
+
// ParamInt returns the int representation of the key's path named parameter's value
// same as GetInt
func (ctx *Context) ParamInt(key string) (int, error) {
@@ -878,7 +984,7 @@ func (ctx *Context) ParamInt64(key string) (int64, error) {
// hasthe form of key1=value1,key2=value2...
func (ctx *Context) ParamsSentence() string {
var buff bytes.Buffer
- ctx.VisitUserValues(func(kb []byte, vg interface{}) {
+ ctx.VisitValues(func(kb []byte, vg interface{}) {
v, ok := vg.(string)
if !ok {
return
@@ -900,197 +1006,179 @@ func (ctx *Context) ParamsSentence() string {
}
-// VisitAllCookies takes a visitor which loops on each (request's) cookie key and value
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// -----------https://github.com/golang/net/blob/master/context/context.go--------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+
+// Deadline returns the time when work done on behalf of this context
+// should be canceled. Deadline returns ok==false when no deadline is
+// set. Successive calls to Deadline return the same results.
+func (ctx *Context) Deadline() (deadline time.Time, ok bool) {
+ return
+}
+
+// Done returns a channel that's closed when work done on behalf of this
+// context should be canceled. Done may return nil if this context can
+// never be canceled. Successive calls to Done return the same value.
//
-// Note: the method ctx.Request.Header.VisitAllCookie by fasthttp, has a strange bug which I cannot solve at the moment.
-// This is the reason which this function exists and should be used instead of fasthttp's built'n.
+// WithCancel arranges for Done to be closed when cancel is called;
+// WithDeadline arranges for Done to be closed when the deadline
+// expires; WithTimeout arranges for Done to be closed when the timeout
+// elapses.
+//
+// Done is provided for use in select statements:
+//
+// // Stream generates values with DoSomething and sends them to out
+// // until DoSomething returns an error or ctx.Done is closed.
+// func Stream(ctx context.Context, out chan<- Value) error {
+// for {
+// v, err := DoSomething(ctx)
+// if err != nil {
+// return err
+// }
+// select {
+// case <-ctx.Done():
+// return ctx.Err()
+// case out <- v:
+// }
+// }
+// }
+//
+// See http://blog.golang.org/pipelines for more examples of how to use
+// a Done channel for cancelation.
+func (ctx *Context) Done() <-chan struct{} {
+ return nil
+}
+
+// Err returns a non-nil error value after Done is closed. Err returns
+// Canceled if the context was canceled or DeadlineExceeded if the
+// context's deadline passed. No other values for Err are defined.
+// After Done is closed, successive calls to Err return the same value.
+func (ctx *Context) Err() error {
+ return nil
+}
+
+// Value returns the value associated with this context for key, or nil
+// if no value is associated with key. Successive calls to Value with
+// the same key returns the same result.
+//
+// Use context values only for request-scoped data that transits
+// processes and API boundaries, not for passing optional parameters to
+// functions.
+//
+// A key identifies a specific value in a Context. Functions that wish
+// to store values in Context typically allocate a key in a global
+// variable then use that key as the argument to context.WithValue and
+// Context.Value. A key can be any type that supports equality;
+// packages should define keys as an unexported type to avoid
+// collisions.
+//
+// Packages that define a Context key should provide type-safe accessors
+// for the values stores using that key:
+//
+// // Package user defines a User type that's stored in Contexts.
+// package user
+//
+// import "golang.org/x/net/context"
+//
+// // User is the type of value stored in the Contexts.
+// type User struct {...}
+//
+// // key is an unexported type for keys defined in this package.
+// // This prevents collisions with keys defined in other packages.
+// type key int
+//
+// // userKey is the key for user.User values in Contexts. It is
+// // unexported; clients use user.NewContext and user.FromContext
+// // instead of using this key directly.
+// var userKey key = 0
+//
+// // NewContext returns a new Context that carries value u.
+// func NewContext(ctx context.Context, u *User) context.Context {
+// return context.WithValue(ctx, userKey, u)
+// }
+//
+// // FromContext returns the User value stored in ctx, if any.
+// func FromContext(ctx context.Context) (*User, bool) {
+// u, ok := ctx.Value(userKey).(*User)
+// return u, ok
+// }
+func (ctx *Context) Value(key interface{}) interface{} {
+ if key == 0 {
+ return ctx.Request
+ }
+ if k, ok := key.(string); ok {
+ return ctx.GetString(k)
+ }
+ return nil
+}
+
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// --------------------------------Session & Cookies------------------------------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+
+// VisitAllCookies takes a visitor which loops on each (request's) cookie key and value
func (ctx *Context) VisitAllCookies(visitor func(key string, value string)) {
- // strange bug, this doesn't works also: cookieHeaderContent := ctx.Request.Header.Peek("Cookie")/User-Agent tested also
- headerbody := string(ctx.Request.Header.Header())
- headerlines := strings.Split(headerbody, "\n")
- for _, s := range headerlines {
- if len(s) > cookieHeaderIDLen {
- if s[0:cookieHeaderIDLen] == cookieHeaderID {
- contents := s[cookieHeaderIDLen:]
- values := strings.Split(contents, "; ")
- for _, s := range values {
- keyvalue := strings.SplitN(s, "=", 2)
- visitor(keyvalue[0], keyvalue[1])
- }
- }
- }
+ for _, cookie := range ctx.Request.Cookies() {
+ visitor(cookie.Name, cookie.Value)
}
}
// GetCookie returns cookie's value by it's name
// returns empty string if nothing was found
-func (ctx *Context) GetCookie(name string) (val string) {
- bcookie := ctx.RequestCtx.Request.Header.Cookie(name)
- if bcookie != nil {
- val = string(bcookie)
+func (ctx *Context) GetCookie(name string) string {
+ cookie, err := ctx.Request.Cookie(name)
+ if err != nil {
+ return ""
}
- return
+ return cookie.Value
}
// SetCookie adds a cookie
-func (ctx *Context) SetCookie(cookie *fasthttp.Cookie) {
- ctx.RequestCtx.Response.Header.SetCookie(cookie)
+func (ctx *Context) SetCookie(cookie *http.Cookie) {
+ http.SetCookie(ctx.ResponseWriter, cookie)
}
// SetCookieKV adds a cookie, receives just a key(string) and a value(string)
-func (ctx *Context) SetCookieKV(key, value string) {
- c := fasthttp.AcquireCookie()
- // c := &fasthttp.Cookie{}
- c.SetKey(key)
- c.SetValue(value)
- c.SetHTTPOnly(true)
- c.SetExpire(time.Now().Add(time.Duration(120) * time.Minute))
+//
+// Expires on 2 hours by default(unchable)
+// use ctx.SetCookie or http.SetCookie instead for more control.
+func (ctx *Context) SetCookieKV(name, value string) {
+ c := &http.Cookie{}
+ c.Name = name
+ c.Value = value
+ c.HttpOnly = true
+ c.Expires = time.Now().Add(time.Duration(120) * time.Minute)
ctx.SetCookie(c)
- fasthttp.ReleaseCookie(c)
}
// RemoveCookie deletes a cookie by it's name/key
func (ctx *Context) RemoveCookie(name string) {
- ctx.Response.Header.DelCookie(name)
-
- cookie := fasthttp.AcquireCookie()
- //cookie := &fasthttp.Cookie{}
- cookie.SetKey(name)
- cookie.SetValue("")
- cookie.SetPath("/")
- cookie.SetHTTPOnly(true)
+ c := &http.Cookie{}
+ c.Name = name
+ c.Value = ""
+ c.Path = "/"
+ c.HttpOnly = true
exp := time.Now().Add(-time.Duration(1) * time.Minute) //RFC says 1 second, but let's do it 1 minute to make sure is working...
- cookie.SetExpire(exp)
- ctx.SetCookie(cookie)
- fasthttp.ReleaseCookie(cookie)
+ c.Expires = exp
+ c.MaxAge = -1
+ ctx.SetCookie(c)
// delete request's cookie also, which is temporarly available
- ctx.Request.Header.DelCookie(name)
+ ctx.Request.Header.Set("Cookie", "")
}
-// GetFlashes returns all the flash messages for available for this request
-func (ctx *Context) GetFlashes() map[string]string {
- // if already taken at least one time, this will be filled
- if messages := ctx.Get(flashMessagesStoreContextKey); messages != nil {
- if m, isMap := messages.(map[string]string); isMap {
- return m
- }
- } else {
- flashMessageFound := false
- // else first time, get all flash cookie keys(the prefix will tell us which is a flash message), and after get all one-by-one using the GetFlash.
- flashMessageCookiePrefixLen := len(flashMessageCookiePrefix)
- ctx.VisitAllCookies(func(key string, value string) {
- if len(key) > flashMessageCookiePrefixLen {
- if key[0:flashMessageCookiePrefixLen] == flashMessageCookiePrefix {
- unprefixedKey := key[flashMessageCookiePrefixLen:]
- _, err := ctx.GetFlash(unprefixedKey) // this func will add to the list (flashMessagesStoreContextKey) also
- if err == nil {
- flashMessageFound = true
- }
- }
-
- }
- })
- // if we found at least one flash message then re-execute this function to return the list
- if flashMessageFound {
- return ctx.GetFlashes()
- }
- }
- return nil
-}
-
-func (ctx *Context) decodeFlashCookie(key string) (string, string) {
- cookieKey := flashMessageCookiePrefix + key
- cookieValue := string(ctx.RequestCtx.Request.Header.Cookie(cookieKey))
-
- if cookieValue != "" {
- v, e := base64.URLEncoding.DecodeString(cookieValue)
- if e == nil {
- return cookieKey, string(v)
- }
- }
- return "", ""
-}
-
-// GetFlash get a flash message by it's key
-// returns the value as string and an error
-//
-// if the cookie doesn't exists the string is empty and the error is filled
-// after the request's life the value is removed
-func (ctx *Context) GetFlash(key string) (string, error) {
-
- // first check if flash exists from this request's lifetime, if yes return that else continue to get the cookie
- storeExists := false
-
- if messages := ctx.Get(flashMessagesStoreContextKey); messages != nil {
- m, isMap := messages.(map[string]string)
- if !isMap {
- return "", fmt.Errorf("Flash store is not a map[string]string. This suppose will never happen, please report this bug.")
- }
- storeExists = true // in order to skip the check later
- for k, v := range m {
- if k == key && v != "" {
- return v, nil
- }
- }
- }
-
- cookieKey, cookieValue := ctx.decodeFlashCookie(key)
- if cookieValue == "" {
- return "", errFlashNotFound
- }
- // store this flash message to the lifetime request's local storage,
- // I choose this method because no need to store it if not used at all
- if storeExists {
- ctx.Get(flashMessagesStoreContextKey).(map[string]string)[key] = cookieValue
- } else {
- flashStoreMap := make(map[string]string)
- flashStoreMap[key] = cookieValue
- ctx.Set(flashMessagesStoreContextKey, flashStoreMap)
- }
-
- //remove the real cookie, no need to have that, we stored it on lifetime request
- ctx.RemoveCookie(cookieKey)
- return cookieValue, nil
- //it should'b be removed until the next reload, so we don't do that: ctx.Request.Header.SetCookie(key, "")
-
-}
-
-// SetFlash sets a flash message, accepts 2 parameters the key(string) and the value(string)
-// the value will be available on the NEXT request
-func (ctx *Context) SetFlash(key string, value string) {
- cKey := flashMessageCookiePrefix + key
- cValue := base64.URLEncoding.EncodeToString([]byte(value))
-
- c := fasthttp.AcquireCookie()
- c.SetKey(cKey)
- c.SetValue(cValue)
- c.SetPath("/")
- c.SetHTTPOnly(true)
- ctx.RequestCtx.Response.Header.SetCookie(c)
- fasthttp.ReleaseCookie(c)
-
- // if any bug on the future: this works, and the above:
- //ctx.RequestCtx.Request.Header.SetCookie(cKey, cValue)
- //ctx.RequestCtx.Response.Header.Add("Set-Cookie", cKey+"="+cValue+"; Path:/; HttpOnly")
- //
-
- /*c := &fasthttp.Cookie{}
- c.SetKey(cKey)
- c.SetValue(cValue)
- c.SetPath("/")
- c.SetHTTPOnly(true)
- ctx.SetCookie(c)*/
-
-}
-
-// Session returns the current session
+// Session returns the current session ( && flash messages )
func (ctx *Context) Session() sessions.Session {
if ctx.framework.sessions == nil { // this should never return nil but FOR ANY CASE, on future changes.
return nil
}
if ctx.session == nil {
- ctx.session = ctx.framework.sessions.StartFasthttp(ctx.RequestCtx)
+ ctx.session = ctx.framework.sessions.Start(ctx.ResponseWriter, ctx.Request)
}
return ctx.session
}
@@ -1098,7 +1186,7 @@ func (ctx *Context) Session() sessions.Session {
// SessionDestroy destroys the whole session, calls the provider's destroy and remove the cookie
func (ctx *Context) SessionDestroy() {
if sess := ctx.Session(); sess != nil {
- ctx.framework.sessions.DestroyFasthttp(ctx.RequestCtx)
+ ctx.framework.sessions.Destroy(ctx.ResponseWriter, ctx.Request)
}
}
@@ -1122,182 +1210,15 @@ func (ctx *Context) MaxAge() int64 {
return -1
}
-// ErrFallback is just an empty error but it is recognised from the TransactionScope.Complete,
-// it reverts its changes and continue as normal, no error will be shown to the user.
-//
-// Usually it is used on recovery from panics (inside .BeginTransaction)
-// but users can use that also to by-pass the error's response of your custom transaction pipe.
-type ErrFallback struct{}
-
-func (ne *ErrFallback) Error() string {
- return ""
-}
-
-// NewErrFallback returns a new error wihch contains an empty error,
-// look .BeginTransaction and context_test.go:TestTransactionRecoveryFromPanic
-func NewErrFallback() *ErrFallback {
- return &ErrFallback{}
-}
-
-// ErrWithStatus custom error type which is useful
-// to send an error containing the http status code and a reason
-type ErrWithStatus struct {
- // failure status code, required
- statusCode int
- // plain text message, optional
- message string // if it's empty then the already registered custom(or default) http error will be fired.
-}
-
-// Silent in case the user changed his/her mind and wants to silence this error
-func (err *ErrWithStatus) Silent() error {
- return NewErrFallback()
-}
-
-// Status sets the http status code of this error
-// if only status exists but no reason then
-// custom http error of this staus (if any) will be fired (context.EmitError)
-func (err *ErrWithStatus) Status(statusCode int) *ErrWithStatus {
- err.statusCode = statusCode
- return err
-}
-
-// Reason sets the reason message of this error
-func (err *ErrWithStatus) Reason(msg string) *ErrWithStatus {
- err.message = msg
- return err
-}
-
-// AppendReason just appends a reason message
-func (err *ErrWithStatus) AppendReason(msg string) *ErrWithStatus {
- err.message += "\n" + msg
- return err
-}
-
-// Error implements the error standard
-func (err ErrWithStatus) Error() string {
- return err.message
-}
-
-// NewErrWithStatus returns an new custom error type which should be used
-// side by side with Transaction(s)
-func NewErrWithStatus() *ErrWithStatus {
- return new(ErrWithStatus)
-}
-
-// TransactionScope is the (request) transaction scope of a handler's context
-// Can't say a lot here because I it will take more than 200 lines to write about.
-// You can search third-party articles or books on how Business Transaction works (it's quite simple, especialy here).
-// But I can provide you a simple example here: https://github.com/iris-contrib/examples/tree/master/transactions
-//
-// Note that this is unique and new
-// (=I haver never seen any other examples or code in Golang on this subject, so far, as with the most of iris features...)
-// it's not covers all paths,
-// such as databases, this should be managed by the libraries you use to make your database connection,
-// this transaction scope is only for iris' request & response(Context).
-type TransactionScope struct {
- Context *Context
- isRequestScoped bool
- isFailure bool
-}
-
-var tspool = sync.Pool{New: func() interface{} { return &TransactionScope{} }}
-
-func acquireTransactionScope(ctx *Context) *TransactionScope {
- ts := tspool.Get().(*TransactionScope)
- ts.Context = ctx
- return ts
-}
-
-func releaseTransactionScope(ts *TransactionScope) {
- ts.Context = nil
- ts.isFailure = false
- ts.isRequestScoped = false
- tspool.Put(ts)
-}
-
-// RequestScoped receives a boolean which determinates if other transactions depends on this.
-// If setted true then whenever this transaction is not completed succesfuly,
-// the rest of the transactions will be not executed at all.
-//
-// Defaults to false, execute all transactions on their own independently scopes.
-func (r *TransactionScope) RequestScoped(isRequestScoped bool) {
- r.isRequestScoped = isRequestScoped
-}
-
-// Complete completes the transaction
-// rollback and send an error when:
-// 1. a not nil error AND non-empty reason AND custom type error has status code
-// 2. a not nil error AND empty reason BUT custom type error has status code
-// 3. a not nil error AND non-empty reason.
-//
-// The error can be a type of ErrWithStatus, create using the iris.NewErrWithStatus().
-func (r *TransactionScope) Complete(err error) {
- if err != nil {
-
- ctx := r.Context
- statusCode := StatusInternalServerError // default http status code if not provided
- reason := err.Error()
- if _, ok := err.(*ErrFallback); ok {
- // revert without any log or response.
- r.isFailure = true
- ctx.Response.Reset()
- return
- }
- if errWstatus, ok := err.(*ErrWithStatus); ok {
- if errWstatus.statusCode > 0 {
- // get the status code from the custom error type
- statusCode = errWstatus.statusCode
-
- // empty error message but status code given,
- if reason == "" {
- r.isFailure = true
- // reset everything, cookies and headers and body.
- ctx.Response.Reset()
- // execute from custom (if any) http error (template or plain text)
- ctx.EmitError(errWstatus.statusCode)
- return
- }
- }
- }
-
- if reason == "" {
- // do nothing empty reason and no status code means that this is not a failure, even if the error is not nil.
- return
- }
-
- // rollback and send an error when we have:
- // 1. a not nil error AND non-empty reason AND custom type error has status code
- // 2. a not nil error AND empty reason BUT custom type error has status code
- // 3. a not nil error AND non-empty reason.
-
- // reset any previous response,
- // except the content type we may use it to fire an error or take that from custom error type (?)
- // no let's keep the custom error type as simple as possible, take that from prev attempt:
- cType := string(ctx.Response.Header.ContentType())
- if cType == "" {
- cType = "text/plain; charset=" + ctx.framework.Config.Charset
- }
-
- // clears:
- // - body
- // - cookies
- // - any headers
- // and anything else we tried to sent before.
- ctx.Response.Reset()
-
- // fire from the error or the custom error type
- ctx.SetStatusCode(statusCode)
- ctx.SetContentType(cType)
- ctx.SetBodyString(reason)
- r.isFailure = true
- return
- }
-
-}
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
+// --------------------------------Transactions-----------------------------------------
+// -------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------
// skipTransactionsContextKey set this to any value to stop executing next transactions
// it's a context-key in order to be used from anywhere, set it by calling the SkipTransactions()
-const skipTransactionsContextKey = "@@IRIS_TRANSACTIONS_SKIP_@@"
+const skipTransactionsContextKey = "__IRIS_TRANSACTIONS_SKIP___"
// SkipTransactions if called then skip the rest of the transactions
// or all of them if called before the first transaction
@@ -1313,31 +1234,23 @@ func (ctx *Context) TransactionsSkipped() bool {
return false
}
-// TransactionFunc is just a func(scope *TransactionScope)
-// used to register transaction(s) to a handler's context.
-type TransactionFunc func(scope *TransactionScope)
-
-// ToMiddleware wraps/converts a transaction to a handler func
-// it is not recommended to be used by users because
-// this can be a little tricky if someone doesn't know how transaction works.
-//
-// Note: it auto-calls the ctx.Next() so as I noted, not recommended to use if you don't know the code behind it,
-// use the .UseTransaction and .DoneTransaction instead
-func (pipe TransactionFunc) ToMiddleware() HandlerFunc {
- return func(ctx *Context) {
- ctx.BeginTransaction(pipe)
- ctx.Next()
- }
-}
-
// non-detailed error log for transacton unexpected panic
var errTransactionInterrupted = errors.New("Transaction Interrupted, recovery from panic:\n%s")
-// BeginTransaction starts a request scoped transaction.
+// BeginTransaction starts a scoped transaction.
+//
+// Can't say a lot here because it will take more than 200 lines to write about.
+// You can search third-party articles or books on how Business Transaction works (it's quite simple, especialy here).
+//
+// Note that this is unique and new
+// (=I haver never seen any other examples or code in Golang on this subject, so far, as with the most of iris features...)
+// it's not covers all paths,
+// such as databases, this should be managed by the libraries you use to make your database connection,
+// this transaction scope is only for context's response.
// Transactions have their own middleware ecosystem also, look iris.go:UseTransaction.
//
// See https://github.com/iris-contrib/examples/tree/master/transactions for more
-func (ctx *Context) BeginTransaction(pipe func(scope *TransactionScope)) {
+func (ctx *Context) BeginTransaction(pipe func(transaction *Transaction)) {
// SILLY NOTE: use of manual pipe type in order of TransactionFunc
// in order to help editors complete the sentence here...
@@ -1346,46 +1259,27 @@ func (ctx *Context) BeginTransaction(pipe func(scope *TransactionScope)) {
if ctx.TransactionsSkipped() {
return
}
-
- // hold the temp context which will be appear and ready-to-use from the pipe.
- tempCtx := *ctx
// get a transaction scope from the pool by passing the temp context/
- scope := acquireTransactionScope(&tempCtx)
+ t := newTransaction(ctx)
defer func() {
if err := recover(); err != nil {
if ctx.framework.Config.IsDevelopment {
ctx.Log(errTransactionInterrupted.Format(err).Error())
}
// complete (again or not , doesn't matters) the scope without loud
- scope.Complete(NewErrFallback())
+ t.Complete(nil)
// we continue as normal, no need to return here*
}
- // if the transaction completed with an error then the transaction itself reverts the changes
- // and replaces the context's response with an error.
- // if the transaction completed successfully then we need to pass the temp's context's response to this context.
- // so we must copy back its context at all cases, no matter the result of the transaction.
-
- // TODO: GREEK, THESE COMMENTS WILL BE REMOVED AFTER I FIX A RECENT BUG REPORTED BY COMMUNITY
- // auto to xreiazomaste gia na pianei ta errors kiolas, ara prepei na vrw allous tropous
- // na manteuw an 9elei o xristis na dixnei errors sto transaction h apla na to kanei skip
- // alla tautoxrona ena failed transaction sto telos na mh kanei reset ta proigoumena, gt twra auto kanei,
- // kai auto xreiazete omws se arketes periptwseis alla prepei kiolas na diaxwrizw to ka9e transaction na einai aneksartita
- // pisteuw to Response.Reset dn 9a mas xreiastei ka9olou, prepei na ktlvenw mono pote xreiazete na kanw copy to scope's context kai pote oxi.
- *ctx = *scope.Context
-
- // if the scope had lifetime of the whole request and it completed with an error(failure)
- // then we do not continue to the next transactions.
- if scope.isRequestScoped && scope.isFailure {
- ctx.SkipTransactions()
- }
-
- // finally, release and put the transaction scope back to the pool.
- releaseTransactionScope(scope)
+ // write the temp contents to the original writer
+ t.Context.ResponseWriter.writeTo(ctx.ResponseWriter)
+ // give back to the transaction the original writer (SetBeforeFlush works this way and only this way)
+ // this is tricky but nessecery if we want ctx.EmitError to work inside transactions
+ t.Context.ResponseWriter = ctx.ResponseWriter
}()
- // run the worker with its context inside this scope.
- pipe(scope)
+ // run the worker with its context clone inside.
+ pipe(t)
}
// Log logs to the iris defined logger
diff --git a/context_test.go b/context_test.go
index 57c4eab7..215cf036 100644
--- a/context_test.go
+++ b/context_test.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"encoding/xml"
"fmt"
+ "net/http"
"net/url"
"strconv"
"strings"
@@ -14,7 +15,6 @@ import (
"github.com/gavv/httpexpect"
"github.com/kataras/iris"
"github.com/kataras/iris/httptest"
- "github.com/valyala/fasthttp"
)
// White-box testing *
@@ -72,7 +72,7 @@ type pathParameters []pathParameter
// White-box testing *
func TestContextParams(t *testing.T) {
- context := &iris.Context{RequestCtx: &fasthttp.RequestCtx{}}
+ context := &iris.Context{}
params := pathParameters{
pathParameter{Key: "testkey", Value: "testvalue"},
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"
iris.Get("/path/:param1/:param2/staticpath/:param3/*anything", func(ctx *iris.Context) {
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)
@@ -136,11 +136,11 @@ func TestContextHostString(t *testing.T) {
iris.ResetDefault()
iris.Default.Config.VHost = "0.0.0.0:8080"
iris.Get("/", func(ctx *iris.Context) {
- ctx.Write(ctx.HostString())
+ ctx.WriteString(ctx.Host())
})
iris.Get("/wrong", func(ctx *iris.Context) {
- ctx.Write(ctx.HostString() + "w")
+ ctx.WriteString(ctx.Host() + "w")
})
e := httptest.New(iris.Default, t)
@@ -155,11 +155,11 @@ func TestContextVirtualHostName(t *testing.T) {
vhost := "mycustomvirtualname.com"
iris.Default.Config.VHost = vhost + ":8080"
iris.Get("/", func(ctx *iris.Context) {
- ctx.Write(ctx.VirtualHostname())
+ ctx.WriteString(ctx.VirtualHostname())
})
iris.Get("/wrong", func(ctx *iris.Context) {
- ctx.Write(ctx.VirtualHostname() + "w")
+ ctx.WriteString(ctx.VirtualHostname() + "w")
})
e := httptest.New(iris.Default, t)
@@ -173,7 +173,7 @@ func TestContextFormValueString(t *testing.T) {
k = "postkey"
v = "postvalue"
iris.Post("/", func(ctx *iris.Context) {
- ctx.Write(k + "=" + ctx.FormValueString(k))
+ ctx.WriteString(k + "=" + ctx.FormValue(k))
})
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.ExplicitURL = true
iris.Party("mysubdomain.").Get("/mypath", func(ctx *iris.Context) {
- ctx.Write(ctx.Subdomain())
+ ctx.WriteString(ctx.Subdomain())
})
e := httptest.New(iris.Default, t)
@@ -341,14 +341,14 @@ func TestContextReadForm(t *testing.T) {
// TestContextRedirectTo tests the named route redirect action
func TestContextRedirectTo(t *testing.T) {
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("/mypostpath", h)("my-post-path")
iris.Get("mypath/with/params/:param1/:param2", func(ctx *iris.Context) {
if l := ctx.ParamsLen(); l != 2 {
t.Fatalf("Strange error, expecting parameters to be two but we got: %d", l)
}
- ctx.Write(ctx.PathString())
+ ctx.WriteString(ctx.Path())
})("my-path-with-params")
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) {
- c := fasthttp.AcquireCookie()
- c.SetKey(key)
- c.SetValue(value)
- c.SetHTTPOnly(true)
- c.SetExpire(time.Now().Add(time.Duration((60 * 60 * 24 * 7 * 4)) * time.Second))
+ c := &http.Cookie{}
+ c.Name = key
+ c.Value = value
+ c.HttpOnly = true
+ c.Expires = time.Now().Add(time.Duration((60 * 60 * 24 * 7 * 4)) * time.Second)
ctx.SetCookie(c)
- fasthttp.ReleaseCookie(c)
})
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) {
@@ -440,7 +439,7 @@ func TestContextCookieSetGetRemove(t *testing.T) {
if cookieFound {
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)
@@ -453,130 +452,6 @@ func TestContextCookieSetGetRemove(t *testing.T) {
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) {
t.Parallel()
values := map[string]interface{}{
@@ -770,9 +645,8 @@ func TestTemplatesDisabled(t *testing.T) {
iris.Default.Config.DisableTemplateEngines = true
file := "index.html"
- ip := "0.0.0.0"
- errTmpl := "Template: %s\nIP: %s
%s"
- 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")
+ errTmpl := "Template: %s
%s"
+ expctedErrMsg := fmt.Sprintf(errTmpl, file, "Error: Unable to execute a template. Trace: Templates are disabled '.Config.DisableTemplatesEngines = true' please turn that to false, as defaulted.\n")
iris.Get("/renderErr", func(ctx *iris.Context) {
ctx.MustRender(file, nil)
@@ -788,46 +662,36 @@ func TestTransactions(t *testing.T) {
secondTransactionSuccessHTMLMessage := "This will sent at all cases because it lives on different transaction and it doesn't fails
"
persistMessage := "I persist show this message to the client!
"
- maybeFailureTransaction := func(shouldFail bool, isRequestScoped bool) func(scope *iris.TransactionScope) {
- return func(scope *iris.TransactionScope) {
- // OPTIONAl, if true then the next transactions will not be executed if this transaction fails
- scope.RequestScoped(isRequestScoped)
+ maybeFailureTransaction := func(shouldFail bool, isRequestScoped bool) func(t *iris.Transaction) {
+ return func(t *iris.Transaction) {
+ // OPTIONAl, the next transactions and the flow will not be skipped if this transaction fails
+ if isRequestScoped {
+ t.SetScope(iris.RequestTransactionScope)
+ }
// OPTIONAL STEP:
// create a new custom type of error here to keep track of the status code and reason message
- err := iris.NewErrWithStatus()
+ err := iris.NewTransactionErrResult()
- // we should use scope.Context if we want to rollback on any errors lives inside this function clojure.
- // 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)
+ t.Context.Text(iris.StatusOK, "Blablabla this should not be sent to the client because we will fill the err with a message and status")
fail := shouldFail
if fail {
- err.Status(iris.StatusInternalServerError).
- // if status given but no reason then the default or the custom http error will be fired (like ctx.EmitError)
- Reason(firstTransactionFailureMessage)
+ err.StatusCode = iris.StatusInternalServerError
+ err.Reason = firstTransactionFailureMessage
}
// OPTIONAl STEP:
// but useful if we want to post back an error message to the client if the transaction failed.
// if the reason is empty then the transaction completed succesfuly,
- // otherwise we rollback the whole response body and cookies and everything lives inside the scope.Request.
- scope.Complete(err)
+ // otherwise we rollback the whole response body and cookies and everything lives inside the transaction.Request.
+ t.Complete(err)
}
}
- successTransaction := func(scope *iris.TransactionScope) {
+ successTransaction := func(scope *iris.Transaction) {
+
scope.Context.HTML(iris.StatusOK,
secondTransactionSuccessHTMLMessage)
// * 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)
})
- /*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) {
ctx.BeginTransaction(maybeFailureTransaction(true, true))
ctx.BeginTransaction(successTransaction)
})
+ customErrorTemplateText := "custom error
"
+ iris.OnError(iris.StatusInternalServerError, func(ctx *iris.Context) {
+ ctx.Text(iris.StatusInternalServerError, customErrorTemplateText)
+ })
+
+ failureWithRegisteredErrorHandler := func(ctx *iris.Context) {
+ ctx.BeginTransaction(func(transaction *iris.Transaction) {
+ transaction.SetScope(iris.RequestTransactionScope)
+ err := iris.NewTransactionErrResult()
+ err.StatusCode = iris.StatusInternalServerError // set only the status code in order to execute the registered template
+ transaction.Complete(err)
+ })
+
+ ctx.Text(iris.StatusOK, "this will not be sent to the client because first is requested scope and it's failed")
+ }
+
+ iris.Get("/failAllBecauseFirstTransactionFailedWithRegisteredErrorTemplate", failureWithRegisteredErrorHandler)
+
e := httptest.New(iris.Default, t)
e.GET("/failFirsTransactionButSuccessSecondWithPersistMessage").
@@ -870,188 +744,24 @@ func TestTransactions(t *testing.T) {
Status(iris.StatusOK).
ContentType("text/html", iris.Config.Charset).
Body().
- Equal(firstTransactionFailureMessage + secondTransactionSuccessHTMLMessage + persistMessage)
+ Equal(secondTransactionSuccessHTMLMessage + persistMessage)
e.GET("/failFirsTransactionButSuccessSecond").
Expect().
Status(iris.StatusOK).
ContentType("text/html", iris.Config.Charset).
Body().
- Equal(firstTransactionFailureMessage + secondTransactionSuccessHTMLMessage)
- /*
- e.GET("/failFirsAndThirdTransactionsButSuccessSecond").
- Expect().
- Status(iris.StatusOK).
- ContentType("text/html", iris.Config.Charset).
- Body().
- Equal(firstTransactionFailureMessage + secondTransactionSuccessHTMLMessage)
- */
+ Equal(secondTransactionSuccessHTMLMessage)
e.GET("/failAllBecauseOfRequestScopeAndFailure").
Expect().
Status(iris.StatusInternalServerError).
Body().
Equal(firstTransactionFailureMessage)
-}
-
-func TestTransactionsMiddleware(t *testing.T) {
- forbiddenMsg := "Error: Not allowed."
- allowMsg := "Hello!"
-
- 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 := "Transaction here"
- 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)
-
+
+ e.GET("/failAllBecauseFirstTransactionFailedWithRegisteredErrorTemplate").
+ Expect().
+ Status(iris.StatusInternalServerError).
+ Body().
+ Equal(customErrorTemplateText)
}
diff --git a/http.go b/http.go
index fd9cf4a5..86b7f15c 100644
--- a/http.go
+++ b/http.go
@@ -1,7 +1,6 @@
package iris
import (
- "bytes"
"crypto/tls"
"log"
"net"
@@ -16,8 +15,6 @@ import (
"github.com/geekypanda/httpcache"
"github.com/iris-contrib/letsencrypt"
"github.com/kataras/go-errors"
- "github.com/valyala/fasthttp"
- "github.com/valyala/fasthttp/fasthttpadaptor"
"golang.org/x/crypto/acme/autocert"
)
@@ -45,171 +42,139 @@ const (
var (
// AllMethods "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", "PATCH", "OPTIONS", "TRACE"
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 (
- // StatusContinue http status '100'
- StatusContinue = 100
- // StatusSwitchingProtocols http status '101'
- StatusSwitchingProtocols = 101
- // StatusOK http status '200'
- StatusOK = 200
- // StatusCreated http status '201'
- StatusCreated = 201
- // StatusAccepted http status '202'
- StatusAccepted = 202
- // StatusNonAuthoritativeInfo http status '203'
- StatusNonAuthoritativeInfo = 203
- // StatusNoContent http status '204'
- StatusNoContent = 204
- // StatusResetContent http status '205'
- StatusResetContent = 205
- // StatusPartialContent http status '206'
- StatusPartialContent = 206
- // StatusMultipleChoices http status '300'
- StatusMultipleChoices = 300
- // StatusMovedPermanently http status '301'
- StatusMovedPermanently = 301
- // StatusFound http status '302'
- StatusFound = 302
- // StatusSeeOther http status '303'
- StatusSeeOther = 303
- // StatusNotModified http status '304'
- StatusNotModified = 304
- // StatusUseProxy http status '305'
- StatusUseProxy = 305
- // StatusTemporaryRedirect http status '307'
- StatusTemporaryRedirect = 307
- // StatusBadRequest http status '400'
- StatusBadRequest = 400
- // StatusUnauthorized http status '401'
- StatusUnauthorized = 401
- // StatusPaymentRequired http status '402'
- StatusPaymentRequired = 402
- // StatusForbidden http status '403'
- StatusForbidden = 403
- // StatusNotFound http status '404'
- StatusNotFound = 404
- // StatusMethodNotAllowed http status '405'
- StatusMethodNotAllowed = 405
- // StatusNotAcceptable http status '406'
- StatusNotAcceptable = 406
- // StatusProxyAuthRequired http status '407'
- StatusProxyAuthRequired = 407
- // StatusRequestTimeout http status '408'
- StatusRequestTimeout = 408
- // StatusConflict http status '409'
- StatusConflict = 409
- // StatusGone http status '410'
- StatusGone = 410
- // StatusLengthRequired http status '411'
- StatusLengthRequired = 411
- // StatusPreconditionFailed http status '412'
- StatusPreconditionFailed = 412
- // StatusRequestEntityTooLarge http status '413'
- StatusRequestEntityTooLarge = 413
- // StatusRequestURITooLong http status '414'
- StatusRequestURITooLong = 414
- // StatusUnsupportedMediaType http status '415'
- StatusUnsupportedMediaType = 415
- // 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
+ StatusContinue = 100 // RFC 7231, 6.2.1
+ StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
+ StatusProcessing = 102 // RFC 2518, 10.1
+
+ StatusOK = 200 // RFC 7231, 6.3.1
+ StatusCreated = 201 // RFC 7231, 6.3.2
+ StatusAccepted = 202 // RFC 7231, 6.3.3
+ StatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4
+ StatusNoContent = 204 // RFC 7231, 6.3.5
+ StatusResetContent = 205 // RFC 7231, 6.3.6
+ StatusPartialContent = 206 // RFC 7233, 4.1
+ StatusMultiStatus = 207 // RFC 4918, 11.1
+ StatusAlreadyReported = 208 // RFC 5842, 7.1
+ StatusIMUsed = 226 // RFC 3229, 10.4.1
+
+ StatusMultipleChoices = 300 // RFC 7231, 6.4.1
+ StatusMovedPermanently = 301 // RFC 7231, 6.4.2
+ StatusFound = 302 // RFC 7231, 6.4.3
+ StatusSeeOther = 303 // RFC 7231, 6.4.4
+ StatusNotModified = 304 // RFC 7232, 4.1
+ StatusUseProxy = 305 // RFC 7231, 6.4.5
+ _ = 306 // RFC 7231, 6.4.6 (Unused)
+ StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7
+ StatusPermanentRedirect = 308 // RFC 7538, 3
+
+ StatusBadRequest = 400 // RFC 7231, 6.5.1
+ StatusUnauthorized = 401 // RFC 7235, 3.1
+ StatusPaymentRequired = 402 // RFC 7231, 6.5.2
+ StatusForbidden = 403 // RFC 7231, 6.5.3
+ StatusNotFound = 404 // RFC 7231, 6.5.4
+ StatusMethodNotAllowed = 405 // RFC 7231, 6.5.5
+ StatusNotAcceptable = 406 // RFC 7231, 6.5.6
+ StatusProxyAuthRequired = 407 // RFC 7235, 3.2
+ StatusRequestTimeout = 408 // RFC 7231, 6.5.7
+ StatusConflict = 409 // RFC 7231, 6.5.8
+ StatusGone = 410 // RFC 7231, 6.5.9
+ StatusLengthRequired = 411 // RFC 7231, 6.5.10
+ StatusPreconditionFailed = 412 // RFC 7232, 4.2
+ StatusRequestEntityTooLarge = 413 // RFC 7231, 6.5.11
+ StatusRequestURITooLong = 414 // RFC 7231, 6.5.12
+ StatusUnsupportedMediaType = 415 // RFC 7231, 6.5.13
+ StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4
+ StatusExpectationFailed = 417 // RFC 7231, 6.5.14
+ StatusTeapot = 418 // RFC 7168, 2.3.3
+ StatusUnprocessableEntity = 422 // RFC 4918, 11.2
+ StatusLocked = 423 // RFC 4918, 11.3
+ StatusFailedDependency = 424 // RFC 4918, 11.4
+ StatusUpgradeRequired = 426 // RFC 7231, 6.5.15
+ StatusPreconditionRequired = 428 // RFC 6585, 3
+ StatusTooManyRequests = 429 // RFC 6585, 4
+ StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5
+ StatusUnavailableForLegalReasons = 451 // RFC 7725, 3
+
+ StatusInternalServerError = 500 // RFC 7231, 6.6.1
+ StatusNotImplemented = 501 // RFC 7231, 6.6.2
+ StatusBadGateway = 502 // RFC 7231, 6.6.3
+ StatusServiceUnavailable = 503 // RFC 7231, 6.6.4
+ StatusGatewayTimeout = 504 // RFC 7231, 6.6.5
+ StatusHTTPVersionNotSupported = 505 // RFC 7231, 6.6.6
+ StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1
+ StatusInsufficientStorage = 507 // RFC 4918, 11.5
+ StatusLoopDetected = 508 // RFC 5842, 7.2
+ StatusNotExtended = 510 // RFC 2774, 7
+ StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
)
var statusText = map[int]string{
- StatusContinue: "Continue",
- StatusSwitchingProtocols: "Switching Protocols",
- StatusOK: "OK",
- StatusCreated: "Created",
- StatusAccepted: "Accepted",
- StatusNonAuthoritativeInfo: "Non-Authoritative Information",
- StatusNoContent: "No Content",
- StatusResetContent: "Reset Content",
- StatusPartialContent: "Partial Content",
- StatusMultipleChoices: "Multiple Choices",
- StatusMovedPermanently: "Moved Permanently",
- StatusFound: "Found",
- StatusSeeOther: "See Other",
- StatusNotModified: "Not Modified",
- StatusUseProxy: "Use Proxy",
- StatusTemporaryRedirect: "Temporary Redirect",
- StatusBadRequest: "Bad Request",
- StatusUnauthorized: "Unauthorized",
- StatusPaymentRequired: "Payment Required",
- StatusForbidden: "Forbidden",
- StatusNotFound: "Not Found",
- StatusMethodNotAllowed: "Method Not Allowed",
- StatusNotAcceptable: "Not Acceptable",
- StatusProxyAuthRequired: "Proxy Authentication Required",
- StatusRequestTimeout: "Request Timeout",
- StatusConflict: "Conflict",
- StatusGone: "Gone",
- StatusLengthRequired: "Length Required",
- StatusPreconditionFailed: "Precondition Failed",
- StatusRequestEntityTooLarge: "Request Entity Too Large",
- StatusRequestURITooLong: "Request URI Too Long",
- StatusUnsupportedMediaType: "Unsupported Media Type",
- StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
- StatusExpectationFailed: "Expectation Failed",
- StatusTeapot: "I'm a teapot",
- StatusPreconditionRequired: "Precondition Required",
- StatusTooManyRequests: "Too Many Requests",
- StatusRequestHeaderFieldsTooLarge: "Request Header Fields Too Large",
- StatusUnavailableForLegalReasons: "Unavailable For Legal Reasons",
+ StatusContinue: "Continue",
+ StatusSwitchingProtocols: "Switching Protocols",
+ StatusProcessing: "Processing",
+
+ StatusOK: "OK",
+ StatusCreated: "Created",
+ StatusAccepted: "Accepted",
+ StatusNonAuthoritativeInfo: "Non-Authoritative Information",
+ StatusNoContent: "No Content",
+ StatusResetContent: "Reset Content",
+ StatusPartialContent: "Partial Content",
+ StatusMultiStatus: "Multi-Status",
+ StatusAlreadyReported: "Already Reported",
+ StatusIMUsed: "IM Used",
+
+ StatusMultipleChoices: "Multiple Choices",
+ StatusMovedPermanently: "Moved Permanently",
+ StatusFound: "Found",
+ StatusSeeOther: "See Other",
+ StatusNotModified: "Not Modified",
+ StatusUseProxy: "Use Proxy",
+ StatusTemporaryRedirect: "Temporary Redirect",
+ StatusPermanentRedirect: "Permanent Redirect",
+
+ StatusBadRequest: "Bad Request",
+ StatusUnauthorized: "Unauthorized",
+ StatusPaymentRequired: "Payment Required",
+ StatusForbidden: "Forbidden",
+ StatusNotFound: "Not Found",
+ StatusMethodNotAllowed: "Method Not Allowed",
+ StatusNotAcceptable: "Not Acceptable",
+ StatusProxyAuthRequired: "Proxy Authentication Required",
+ StatusRequestTimeout: "Request Timeout",
+ StatusConflict: "Conflict",
+ StatusGone: "Gone",
+ StatusLengthRequired: "Length Required",
+ StatusPreconditionFailed: "Precondition Failed",
+ StatusRequestEntityTooLarge: "Request Entity Too Large",
+ StatusRequestURITooLong: "Request URI Too Long",
+ StatusUnsupportedMediaType: "Unsupported Media Type",
+ StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
+ StatusExpectationFailed: "Expectation Failed",
+ StatusTeapot: "I'm a teapot",
+ 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",
StatusNotImplemented: "Not Implemented",
StatusBadGateway: "Bad Gateway",
StatusServiceUnavailable: "Service Unavailable",
StatusGatewayTimeout: "Gateway Timeout",
StatusHTTPVersionNotSupported: "HTTP Version Not Supported",
+ StatusVariantAlsoNegotiates: "Variant Also Negotiates",
+ StatusInsufficientStorage: "Insufficient Storage",
+ StatusLoopDetected: "Loop Detected",
+ StatusNotExtended: "Not Extended",
StatusNetworkAuthenticationRequired: "Network Authentication Required",
}
@@ -226,7 +191,7 @@ var errHandler = errors.New("Passed argument is not func(*Context) neither an ob
type (
// Handler the main Iris Handler interface.
Handler interface {
- Serve(ctx *Context)
+ Serve(ctx *Context) // iris-specific
}
// HandlerFunc type is an adapter to allow the use of
@@ -246,37 +211,36 @@ func (h HandlerFunc) Serve(ctx *Context) {
h(ctx)
}
-// ToHandler converts an http.Handler or http.HandlerFunc to an iris.Handler
-func ToHandler(handler interface{}) Handler {
+// ToNativeHandler converts an iris handler to http.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.
switch handler.(type) {
- case Handler:
+ case HandlerFunc:
//it's already an iris handler
- return handler.(Handler)
+ return handler.(HandlerFunc)
case http.Handler:
//it's http.Handler
- h := fasthttpadaptor.NewFastHTTPHandlerFunc(handler.(http.Handler).ServeHTTP)
-
- return ToHandlerFastHTTP(h)
+ h := handler.(http.Handler)
+ return func(ctx *Context) {
+ h.ServeHTTP(ctx.ResponseWriter, ctx.Request)
+ }
case func(http.ResponseWriter, *http.Request):
- //it's http.HandlerFunc
- h := fasthttpadaptor.NewFastHTTPHandlerFunc(handler.(func(http.ResponseWriter, *http.Request)))
- return ToHandlerFastHTTP(h)
+ return ToHandler(http.HandlerFunc(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
default:
panic(errHandler.Format(handler, handler))
}
-}
-// ToHandlerFunc converts an http.Handler or http.HandlerFunc to an iris.HandlerFunc
-func ToHandlerFunc(handler interface{}) HandlerFunc {
- return ToHandler(handler).Serve
-}
-
-// ToHandlerFastHTTP converts an fasthttp.RequestHandler to an iris.Handler
-func ToHandlerFastHTTP(h fasthttp.RequestHandler) Handler {
- return HandlerFunc((func(ctx *Context) {
- h(ctx.RequestCtx)
- }))
}
// convertToHandlers 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,
// reason: no clojures hell in the Cache function
type cachedMuxEntry struct {
- cachedHandler fasthttp.RequestHandler
+ cachedHandler http.Handler
}
-func newCachedMuxEntry(f *Framework, bodyHandler HandlerFunc, expiration time.Duration) *cachedMuxEntry {
- fhandler := func(reqCtx *fasthttp.RequestCtx) {
- ctx := f.AcquireCtx(reqCtx)
+func newCachedMuxEntry(s *Framework, bodyHandler HandlerFunc, expiration time.Duration) *cachedMuxEntry {
+ httphandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ ctx := s.AcquireCtx(w, r)
bodyHandler.Serve(ctx)
- f.ReleaseCtx(ctx)
- }
+ s.ReleaseCtx(ctx)
+ })
- cachedHandler := httpcache.CacheFasthttpFunc(fhandler, expiration)
+ cachedHandler := httpcache.Cache(httphandler, expiration)
return &cachedMuxEntry{
cachedHandler: cachedHandler,
}
}
func (c *cachedMuxEntry) Serve(ctx *Context) {
- c.cachedHandler(ctx.RequestCtx)
+ c.cachedHandler.ServeHTTP(ctx.ResponseWriter, ctx.Request)
}
type (
@@ -743,8 +707,7 @@ type (
// if no name given then it's the subdomain+path
name string
subdomain string
- method []byte
- methodStr string
+ method string
path string
middleware Middleware
formattedPath string
@@ -767,8 +730,8 @@ func (s bySubdomain) Less(i, j int) bool {
var _ Route = &route{}
-func newRoute(method []byte, subdomain string, path string, middleware Middleware) *route {
- r := &route{name: path + subdomain, method: method, methodStr: string(method), subdomain: subdomain, path: path, middleware: middleware}
+func newRoute(method string, subdomain string, path string, middleware Middleware) *route {
+ r := &route{name: path + subdomain, method: method, subdomain: subdomain, path: path, middleware: middleware}
r.formatPath()
return r
}
@@ -816,10 +779,7 @@ func (r route) Subdomain() string {
}
func (r route) Method() string {
- if r.methodStr == "" {
- r.methodStr = string(r.method)
- }
- return r.methodStr
+ return r.method
}
func (r route) Path() string {
@@ -865,7 +825,7 @@ const (
type (
muxTree struct {
- method []byte
+ method string
// subdomain is empty for default-hostname routes,
// ex: mysubdomain.
subdomain string
@@ -886,9 +846,6 @@ type (
hostname string
// if any of the trees contains not empty subdomain
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/
// defaults to true
correctPath bool
@@ -904,7 +861,6 @@ func newServeMux(logger *log.Logger) *serveMux {
lookups: make([]*route, 0),
errorHandlers: make(map[int]Handler, 0),
hostname: DefaultServerHostname, // these are changing when the server is up
- escapePath: !DefaultDisablePathEscape,
correctPath: !DefaultDisablePathCorrection,
fireMethodNotAllowed: false,
logger: logger,
@@ -917,10 +873,6 @@ func (mux *serveMux) setHostname(h string) {
mux.hostname = h
}
-func (mux *serveMux) setEscapePath(b bool) {
- mux.escapePath = b
-}
-
func (mux *serveMux) setCorrectPath(b bool) {
mux.correctPath = b
}
@@ -948,7 +900,7 @@ func (mux *serveMux) fireError(statusCode int, ctx *Context) {
errHandler := mux.errorHandlers[statusCode]
if errHandler == nil {
errHandler = HandlerFunc(func(ctx *Context) {
- ctx.Response.Reset()
+ ctx.ResponseWriter.Reset()
ctx.SetStatusCode(statusCode)
ctx.SetBodyString(statusText[statusCode])
})
@@ -959,17 +911,17 @@ func (mux *serveMux) fireError(statusCode int, ctx *Context) {
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 {
t := mux.garden[i]
- if bytes.Equal(t.method, method) && t.subdomain == subdomain {
+ if t.method == method && t.subdomain == subdomain {
return t
}
}
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()
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
// 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))
@@ -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 :)
- getRequestPath = func(reqCtx *fasthttp.RequestCtx) string {
- 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)
+ methodEqual = func(reqMethod string, treeMethod string) bool {
+ return reqMethod == treeMethod
}
// check for cors conflicts FIRST in order to put them in OPTIONS tree also
for i := range mux.lookups {
r := mux.lookups[i]
if r.hasCors() {
// 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
- return bytes.Equal(reqMethod, MethodOptionsBytes) || bytes.Equal(reqMethod, treeMethod)
+ return reqMethod == MethodOptions || reqMethod == treeMethod
}
break
}
@@ -1072,18 +1015,19 @@ func HTMLEscape(s string) string {
func (mux *serveMux) BuildHandler() HandlerFunc {
// initialize the router once
- getRequestPath, methodEqual := mux.build()
+ methodEqual := mux.build()
return func(context *Context) {
- routePath := getRequestPath(context.RequestCtx)
+ routePath := context.Path()
for i := range mux.garden {
tree := mux.garden[i]
- if !methodEqual(context.Method(), tree.method) {
+ if !methodEqual(context.Request.Method, tree.method) {
continue
}
if mux.hosts && tree.subdomain != "" {
- // context.VirtualHost() is a slow method because it makes string.Replaces but user can understand that if subdomain then server will have some nano/or/milleseconds performance cost
+ // 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()
if requestHost != mux.hostname {
//println(requestHost + " != " + mux.hostname)
@@ -1111,8 +1055,7 @@ func (mux *serveMux) BuildHandler() HandlerFunc {
//ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent)
context.Do()
return
- } else if mustRedirect && mux.correctPath && !bytes.Equal(context.Method(), MethodConnectBytes) {
-
+ } else if mustRedirect && mux.correctPath { // && context.Method() == MethodConnect {
reqPath := routePath
pathLen := len(reqPath)
@@ -1124,23 +1067,22 @@ func (mux *serveMux) BuildHandler() HandlerFunc {
reqPath = reqPath + "/"
}
- context.Request.URI().SetPath(reqPath)
- urlToRedirect := string(context.Request.RequestURI())
+ urlToRedirect := reqPath
- statisForRedirect := StatusMovedPermanently // StatusMovedPermanently, this document is obselte, clients caches this.
- if bytes.Equal(tree.method, MethodPostBytes) ||
- bytes.Equal(tree.method, MethodPutBytes) ||
- bytes.Equal(tree.method, MethodDeleteBytes) {
- statisForRedirect = StatusTemporaryRedirect // To maintain POST data
+ statusForRedirect := StatusMovedPermanently // StatusMovedPermanently, this document is obselte, clients caches this.
+ if tree.method == MethodPost ||
+ tree.method == MethodPut ||
+ tree.method == MethodDelete {
+ 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
// response because older user agents may not understand 301/307.
// Shouldn't send the response for POST or HEAD; that leaves GET.
- if bytes.Equal(tree.method, MethodGetBytes) {
+ if tree.method == MethodGet {
note := "Moved Permanently.\n"
- context.Write(note)
+ context.WriteString(note)
}
return
}
@@ -1408,13 +1350,14 @@ func ParseScheme(domain string) string {
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
-var ProxyHandler = func(proxyAddr string, redirectSchemeAndHost string) fasthttp.RequestHandler {
- return func(reqCtx *fasthttp.RequestCtx) {
+// ProxyHandler returns a new net/http.Handler which works as 'proxy', maybe doesn't suits you look its code before using that in production
+var ProxyHandler = func(redirectSchemeAndHost string) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
// override the handler and redirect all requests to this addr
redirectTo := redirectSchemeAndHost
- fakehost := string(reqCtx.Request.Host())
- path := string(reqCtx.Path())
+ fakehost := r.URL.Host
+ path := r.URL.EscapedPath()
if strings.Count(fakehost, ".") >= 3 { // propably a subdomain, pure check but doesn't matters don't worry
if sufIdx := strings.LastIndexByte(fakehost, '.'); sufIdx > 0 {
// check if the last part is a number instead of .com/.gr...
@@ -1425,7 +1368,7 @@ var ProxyHandler = func(proxyAddr string, redirectSchemeAndHost string) fasthttp
realHost := strings.Replace(redirectSchemeAndHost, redirectScheme, "", 1)
redirectHost := strings.Replace(fakehost, fakehost, realHost, 1)
redirectTo = redirectScheme + redirectHost + path
- reqCtx.Redirect(redirectTo, StatusMovedPermanently)
+ http.Redirect(w, r, redirectTo, StatusMovedPermanently)
return
}
}
@@ -1433,8 +1376,11 @@ var ProxyHandler = func(proxyAddr string, redirectSchemeAndHost string) fasthttp
if 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)
// override the handler and redirect all requests to this addr
- h := ProxyHandler(proxyAddr, redirectSchemeAndHost)
+ h := ProxyHandler(redirectSchemeAndHost)
prx := New(OptionDisableBanner(true))
prx.Router = h
go prx.Listen(proxyAddr)
-
- return prx.Close
+ if ok := <-prx.Available; !ok {
+ prx.Logger.Panic("Unexpected error: proxy server cannot start, please report this as bug!!")
+ }
+ return func() error { return prx.Close() }
}
diff --git a/http_test.go b/http_test.go
index 521e7cf1..234426fd 100644
--- a/http_test.go
+++ b/http_test.go
@@ -3,70 +3,69 @@ package iris_test
import (
"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"
"math/rand"
+ "net/http"
"os"
"strconv"
- "sync/atomic"
"testing"
"time"
+
+ "github.com/gavv/httpexpect"
+ "github.com/kataras/iris"
+ "github.com/kataras/iris/httptest"
)
const (
testTLSCert = `-----BEGIN CERTIFICATE-----
-MIIDATCCAemgAwIBAgIJAPdE0ZyCKwVtMA0GCSqGSIb3DQEBBQUAMBcxFTATBgNV
-BAMMDG15ZG9tYWluLmNvbTAeFw0xNjA5MjQwNjU3MDVaFw0yNjA5MjIwNjU3MDVa
-MBcxFTATBgNVBAMMDG15ZG9tYWluLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP
-ADCCAQoCggEBAM9YJOV1Bl+NwEq8ZAcVU2YONBw5zGkUFJUZkL77XT0i1V473JTf
-GEpNZisDman+6n+pXarC2mR4T9PkCfmk072HaZ2LXwYe9XSgxnLJZJA1fJMzdMMC
-2XveINF+/eeoyW9+8ZjQPbZdHWcxi7RomXg1AOMAG2UWMjalK5xkTHcqDuOI2LEe
-mezWHnFdBJNMTi3pNdbVr7BjexZTSGhx4LAIP2ufTUoVzk+Cvyr4IhS00zOiyyWv
-tuJaO20Q0Q5n34o9vDAACKAfNRLBE8qzdRwsjMumXTX3hJzvgFp/4Lr5Hr2I2fBd
-tbIWN9xIsu6IibBGFItiAfQSrKAR7IFVqDUCAwEAAaNQME4wHQYDVR0OBBYEFNvN
-Yik2eBRDmDaqoMaLfvr75kGfMB8GA1UdIwQYMBaAFNvNYik2eBRDmDaqoMaLfvr7
-5kGfMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEAv3pKkmDTXIChB
-nVrbYwNibin9HYOPf3JCjU48V672YPgbfJM0WfTvLXNVBOdTz3aIUrhfwv/go2Jz
-yDcIFdLUdwllovhj1RwI96lbgCJ4AKoO/fvJ5Rxq+/vvLYB38PNl/fVEnOOeWzCQ
-qHfjckShNV5GzJPhfpYn9Gj6+Zj3O0cJXhF9L/FlbVxFhwPjPRbFDNTHYzgiHy82
-zhhDhTQJVnNJXfokdlg9OjfFkySqpv9fvPi/dfk5j1KmKUiYP5SYUhZiKX1JScvx
-JgesCwz1nUfVQ1TYE0dphU8mTCN4Y42i7bctx7iLcDRIFhtYstt0hhCKSxFmJSBG
-y9RITRA=
+MIIDAzCCAeugAwIBAgIJAP0pWSuIYyQCMA0GCSqGSIb3DQEBBQUAMBgxFjAUBgNV
+BAMMDWxvY2FsaG9zdDozMzEwHhcNMTYxMjI1MDk1OTI3WhcNMjYxMjIzMDk1OTI3
+WjAYMRYwFAYDVQQDDA1sb2NhbGhvc3Q6MzMxMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA5vETjLa+8W856rWXO1xMF/CLss9vn5xZhPXKhgz+D7ogSAXm
+mWP53eeBUGC2r26J++CYfVqwOmfJEu9kkGUVi8cGMY9dHeIFPfxD31MYX175jJQe
+tu0WeUII7ciNsSUDyBMqsl7yi1IgN7iLONM++1+QfbbmNiEbghRV6icEH6M+bWlz
+3YSAMEdpK3mg2gsugfLKMwJkaBKEehUNMySRlIhyLITqt1exYGaggRd1zjqUpqpD
+sL2sRVHJ3qHGkSh8nVy8MvG8BXiFdYQJP3mCQDZzruCyMWj5/19KAyu7Cto3Bcvu
+PgujnwRtU+itt8WhZUVtU1n7Ivf6lMJTBcc4OQIDAQABo1AwTjAdBgNVHQ4EFgQU
+MXrBvbILQmiwjUj19aecF2N+6IkwHwYDVR0jBBgwFoAUMXrBvbILQmiwjUj19aec
+F2N+6IkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA4zbFml1t9KXJ
+OijAV8gALePR8v04DQwJP+jsRxXw5zzhc8Wqzdd2hjUd07mfRWAvmyywrmhCV6zq
+OHznR+aqIqHtm0vV8OpKxLoIQXavfBd6axEXt3859RDM4xJNwIlxs3+LWGPgINud
+wjJqjyzSlhJpQpx4YZ5Da+VMiqAp8N1UeaZ5lBvmSDvoGh6HLODSqtPlWMrldRW9
+AfsXVxenq81MIMeKW2fSOoPnWZ4Vjf1+dSlbJE/DD4zzcfbyfgY6Ep/RrUltJ3ag
+FQbuNTQlgKabe21dSL9zJ2PengVKXl4Trl+4t/Kina9N9Jw535IRCSwinD6a/2Ca
+m7DnVXFiVA==
-----END CERTIFICATE-----
`
testTLSKey = `-----BEGIN RSA PRIVATE KEY-----
-MIIEpQIBAAKCAQEAz1gk5XUGX43ASrxkBxVTZg40HDnMaRQUlRmQvvtdPSLVXjvc
-lN8YSk1mKwOZqf7qf6ldqsLaZHhP0+QJ+aTTvYdpnYtfBh71dKDGcslkkDV8kzN0
-wwLZe94g0X7956jJb37xmNA9tl0dZzGLtGiZeDUA4wAbZRYyNqUrnGRMdyoO44jY
-sR6Z7NYecV0Ek0xOLek11tWvsGN7FlNIaHHgsAg/a59NShXOT4K/KvgiFLTTM6LL
-Ja+24lo7bRDRDmffij28MAAIoB81EsETyrN1HCyMy6ZdNfeEnO+AWn/guvkevYjZ
-8F21shY33Eiy7oiJsEYUi2IB9BKsoBHsgVWoNQIDAQABAoIBABRhi67qY+f8nQw7
-nHF9zSbY+pJTtB4YFTXav3mmZ7HcvLB4neQcUdzr4sETp4UoQ5Cs60IfySvbD626
-WqipZQ7aQq1zx7FoVaRTMW6TEUmDmG03v6BzpUEhwoQVQYwF8Vb+WW01+vr0CDHe
-kub26S8BtsaZehfjqKfqcHD9Au8ri+Nwbu91iT4POVzBBBwYbtwXZwaYDR5PCNOI
-ld+6qLapVIVKpvLHL+tA4A/n0n4l7p8TJo/qYesFRZ7J+USt4YGFDuf15nnDge/7
-9Qjyqa9WmvRGytPdgtEzc8XwJu7xhcRthSmCppdY0ExHBwVceCEz8u3QbRYFqq3U
-iLXUpfkCgYEA6JMlRtLIEAPkJZGBxJBSaeWVOeaAaMnLAdcu4OFjSuxr65HXsdhM
-aWHopCE44NjBROAg67qgc5vNBZvhnCwyTI8nb6k/CO5QZ4AG1d2Xe/9rPV5pdaBL
-gRgOJjlG0apZpPVM4I/0JU5prwS2Z71lFmEMikwmbmngYmOysqUBfbMCgYEA5Dpw
-qzn1Oq+fasSUjzUa/wvBTVMpVjnNrda7Hnlx6lssnQaPFqifFYL9Zr/+5rWdsE2O
-NNCsy68tacspAUa0LQinzpeSP4dyTVCvZG/xWPaYDKE6FDmzsi8xHBTTxMneeU6p
-HUKJMUD3LcvBiCLunhT2xd1/+LKzVce6ms9R3ncCgYEAv9wjdDmOMSgEnblbg/xL
-AHEUmZ89bzSI9Au/8GP+tWAz5zF47o2w+355nGyLr3EgfuEmR1C97KEqkOX3SA5t
-sBqoPcUw6v0t9zP2b5dN0Ez0+rtX5GFH6Ecf5Qh7E5ukOCDkOpyGnAADzw3kK9Bi
-BAQrhCstyQguwvvb/uOAR2ECgYEA3nYcZrqaz7ZqVL8C88hW5S4HIKEkFNlJI97A
-DAdiw4ZVqUXAady5HFXPPL1+8FEtQLGIIPEazXuWb53I/WZ2r8LVFunlcylKgBRa
-sjLvdMEBGqZ5H0fTYabgXrfqZ9JBmcrTyyKU6b6icTBAF7u9DbfvhpTObZN6fO2v
-dcEJ0ycCgYEAxM8nGR+pa16kZmW1QV62EN0ifrU7SOJHCOGApU0jvTz8D4GO2j+H
-MsoPSBrZ++UYgtGO/dK4aBV1JDdy8ZdyfE6UN+a6dQgdNdbOMT4XwWdS0zlZ/+F4
-PKvbgZnLEKHvjODJ65aQmcTVUoaa11J29iAAtA3a3TcMn6C2fYpRy1s=
+MIIEpAIBAAKCAQEA5vETjLa+8W856rWXO1xMF/CLss9vn5xZhPXKhgz+D7ogSAXm
+mWP53eeBUGC2r26J++CYfVqwOmfJEu9kkGUVi8cGMY9dHeIFPfxD31MYX175jJQe
+tu0WeUII7ciNsSUDyBMqsl7yi1IgN7iLONM++1+QfbbmNiEbghRV6icEH6M+bWlz
+3YSAMEdpK3mg2gsugfLKMwJkaBKEehUNMySRlIhyLITqt1exYGaggRd1zjqUpqpD
+sL2sRVHJ3qHGkSh8nVy8MvG8BXiFdYQJP3mCQDZzruCyMWj5/19KAyu7Cto3Bcvu
+PgujnwRtU+itt8WhZUVtU1n7Ivf6lMJTBcc4OQIDAQABAoIBAQCTLE0eHpPevtg0
++FaRUMd5diVA5asoF3aBIjZXaU47bY0G+SO02x6wSMmDFK83a4Vpy/7B3Bp0jhF5
+DLCUyKaLdmE/EjLwSUq37ty+JHFizd7QtNBCGSN6URfpmSabHpCjX3uVQqblHIhF
+mki3BQCdJ5CoXPemxUCHjDgYSZb6JVNIPJExjekc0+4A2MYWMXV6Wr86C7AY3659
+KmveZpC3gOkLA/g/IqDQL/QgTq7/3eloHaO+uPBihdF56do4eaOO0jgFYpl8V7ek
+PZhHfhuPZV3oq15+8Vt77ngtjUWVI6qX0E3ilh+V5cof+03q0FzHPVe3zBUNXcm0
+OGz19u/FAoGBAPSm4Aa4xs/ybyjQakMNix9rak66ehzGkmlfeK5yuQ/fHmTg8Ac+
+ahGs6A3lFWQiyU6hqm6Qp0iKuxuDh35DJGCWAw5OUS/7WLJtu8fNFch6iIG29rFs
+s+Uz2YLxJPebpBsKymZUp7NyDRgEElkiqsREmbYjLrc8uNKkDy+k14YnAoGBAPGn
+ZlN0Mo5iNgQStulYEP5pI7WOOax9KOYVnBNguqgY9c7fXVXBxChoxt5ebQJWG45y
+KPG0hB0bkA4YPu4bTRf5acIMpjFwcxNlmwdc4oCkT4xqAFs9B/AKYZgkf4IfKHqW
+P9PD7TbUpkaxv25bPYwUSEB7lPa+hBtRyN9Wo6qfAoGAPBkeISiU1hJE0i7YW55h
+FZfKZoqSYq043B+ywo+1/Dsf+UH0VKM1ZSAnZPpoVc/hyaoW9tAb98r0iZ620wJl
+VkCjgYklknbY5APmw/8SIcxP6iVq1kzQqDYjcXIRVa3rEyWEcLzM8VzL8KFXbIQC
+lPIRHFfqKuMEt+HLRTXmJ7MCgYAHGvv4QjdmVl7uObqlG9DMGj1RjlAF0VxNf58q
+NrLmVG2N2qV86wigg4wtZ6te4TdINfUcPkmQLYpLz8yx5Z2bsdq5OPP+CidoD5nC
+WqnSTIKGR2uhQycjmLqL5a7WHaJsEFTqHh2wego1k+5kCUzC/KmvM7MKmkl6ICp+
+3qZLUwKBgQCDOhKDwYo1hdiXoOOQqg/LZmpWOqjO3b4p99B9iJqhmXN0GKXIPSBh
+5nqqmGsG8asSQhchs7EPMh8B80KbrDTeidWskZuUoQV27Al1UEmL6Zcl83qXD6sf
+k9X9TwWyZtp5IL1CAEd/Il9ZTXFzr3lNaN8LCFnU+EIsz1YgUW8LTg==
-----END RSA PRIVATE KEY-----
- `
+`
)
func TestParseAddr(t *testing.T) {
@@ -155,7 +154,7 @@ func getRandomNumber(min int, max int) int {
// works as
// defer listenTLS(iris.Default, hostTLS)()
func listenTLS(api *iris.Framework, hostTLS string) func() {
- api.Close() // close any previous server
+ api.Close() // close any prev listener
api.Config.DisableBanner = true
// create the key and cert files on the fly, and delete them when this test finished
certFile, ferr := ioutil.TempFile("", "cert")
@@ -179,68 +178,77 @@ func listenTLS(api *iris.Framework, hostTLS string) func() {
return func() {
certFile.Close()
- time.Sleep(150 * time.Millisecond)
+ time.Sleep(50 * time.Millisecond)
os.Remove(certFile.Name())
keyFile.Close()
- time.Sleep(150 * time.Millisecond)
+ time.Sleep(50 * time.Millisecond)
os.Remove(keyFile.Name())
-
- api.Close()
}
}
// Contains the server test for multi running servers
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)
- hostTLS := host + ":" + strconv.Itoa(getRandomNumber(8886, 8889))
-
- iris.Get("/", func(ctx *iris.Context) {
- ctx.Write("Hello from %s", hostTLS)
+ host := "localhost"
+ hostTLS := host + ":" + strconv.Itoa(getRandomNumber(1919, 2221))
+ api.Get("/", func(ctx *iris.Context) {
+ ctx.Writef("Hello from %s", hostTLS)
})
+ // println("running main on: " + hostTLS)
- proxyHost := host + ":" + strconv.Itoa(getRandomNumber(3300, 3332))
- closeProxy := iris.Proxy(proxyHost, "https://"+hostTLS)
- defer closeProxy()
+ defer listenTLS(api, hostTLS)()
- 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)
- e.Request("GET", "https://"+hostTLS).Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
+ iris.Proxy(proxyHost, "https://"+hostTLS)
+ // proxySrv := &http.Server{Addr: proxyHost, Handler: iris.ProxyHandler("https://" + hostTLS)}
+ // go proxySrv.ListenAndServe()
+ // time.Sleep(3 * time.Second)
+
+ eproxy := httptest.NewInsecure("http://"+proxyHost, t, httptest.ExplicitURL(true))
+ eproxy.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
}
// Contains the server test for multi running servers
func TestMultiRunningServers_v2(t *testing.T) {
- iris.ResetDefault()
+ api := iris.New()
domain := "localhost"
- hostTLS := domain + ":" + strconv.Itoa(getRandomNumber(3333, 4444))
+ hostTLS := domain + ":" + strconv.Itoa(getRandomNumber(2222, 2229))
srv1Host := domain + ":" + strconv.Itoa(getRandomNumber(4446, 5444))
srv2Host := domain + ":" + strconv.Itoa(getRandomNumber(7778, 8887))
- iris.Get("/", func(ctx *iris.Context) {
- ctx.Write("Hello from %s", hostTLS)
+ api.Get("/", func(ctx *iris.Context) {
+ ctx.Writef("Hello from %s", hostTLS)
})
- // using the proxy handler
- fsrv1 := &fasthttp.Server{Handler: iris.ProxyHandler(srv1Host, "https://"+hostTLS)}
- go fsrv1.ListenAndServe(srv1Host)
+ defer listenTLS(api, hostTLS)()
+
// using the same iris' handler but not as proxy, just the same handler
- fsrv2 := &fasthttp.Server{Handler: iris.Default.Router}
- go fsrv2.ListenAndServe(srv2Host)
+ srv2 := &http.Server{Handler: api.Router, Addr: 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)
- e.Request("GET", "http://"+srv2Host).Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
- e.Request("GET", "https://"+hostTLS).Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
+ eproxy1 := httptest.NewInsecure("http://"+srv1Host, t, httptest.ExplicitURL(true))
+ eproxy1.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
+
+ eproxy2 := httptest.NewInsecure("http://"+srv2Host, t)
+ eproxy2.Request("GET", "/").Expect().Status(iris.StatusOK).Body().Equal("Hello from " + hostTLS)
}
@@ -360,7 +368,7 @@ func TestMuxSimple(t *testing.T) {
func TestMuxSimpleParty(t *testing.T) {
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 {
subdomainParty := iris.Party(testSubdomain + ".")
@@ -422,8 +430,9 @@ func TestMuxPathEscape(t *testing.T) {
iris.ResetDefault()
iris.Get("/details/:name", func(ctx *iris.Context) {
- name := ctx.Param("name")
+ name := ctx.ParamDecoded("name")
highlight := ctx.URLParam("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.Get("/encoding/:url", func(ctx *iris.Context) {
- url := iris.DecodeURL(ctx.Param("url"))
+ url := ctx.ParamDecoded("url")
+
ctx.SetStatusCode(iris.StatusOK)
- ctx.Write(url)
+ ctx.WriteString(url)
})
e := httptest.New(iris.Default, t)
@@ -487,11 +497,11 @@ func TestMuxCustomErrors(t *testing.T) {
// register the custom errors
iris.OnError(iris.StatusNotFound, func(ctx *iris.Context) {
- ctx.Write("%s", notFoundMessage)
+ ctx.Writef("%s", notFoundMessage)
})
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
@@ -511,27 +521,27 @@ type testUserAPI struct {
// GET /users
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
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
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
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
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) {
@@ -544,7 +554,7 @@ func TestMuxAPI(t *testing.T) {
ctx.Next()
}, func(ctx *iris.Context) {
if ctx.Get("user") == "username" {
- ctx.Write(middlewareResponseText)
+ ctx.WriteString(middlewareResponseText)
ctx.Next()
} else {
ctx.SetStatusCode(iris.StatusUnauthorized)
@@ -630,11 +640,11 @@ func TestMuxFireMethodNotAllowed(t *testing.T) {
iris.ResetDefault()
iris.Default.Config.FireMethodNotAllowed = true
h := func(ctx *iris.Context) {
- ctx.Write("%s", ctx.MethodString())
+ ctx.WriteString(ctx.Method())
}
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)
@@ -650,6 +660,7 @@ func TestMuxFireMethodNotAllowed(t *testing.T) {
iris.Close()
}
+/*
var (
cacheDuration = 2 * time.Second
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
}
-/*
+
Inside github.com/geekypanda/httpcache are enough, no need to add 10+ seconds of testing here.
func TestCache(t *testing.T) {
@@ -714,15 +725,18 @@ func TestCache(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") })
- iris.Get("/redirected", func(ctx *iris.Context) { ctx.Text(iris.StatusOK, "Redirected to "+ctx.URI().String()) })
+ api := iris.New(iris.OptionIsDevelopment(true))
- 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)
}
diff --git a/httptest/httptest.go b/httptest/httptest.go
index d9c59bba..c272bd8a 100644
--- a/httptest/httptest.go
+++ b/httptest/httptest.go
@@ -1,10 +1,12 @@
package httptest
import (
- "github.com/gavv/httpexpect"
- "github.com/kataras/iris"
+ "crypto/tls"
"net/http"
"testing"
+
+ "github.com/gavv/httpexpect"
+ "github.com/kataras/iris"
)
type (
@@ -94,7 +96,35 @@ func New(api *iris.Framework, t *testing.T, setters ...OptionSetter) *httpexpect
testConfiguration := httpexpect.Config{
BaseURL: baseURL,
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(),
},
Reporter: httpexpect.NewAssertReporter(t),
diff --git a/iris.go b/iris.go
index 5bd62f47..750d7dc7 100644
--- a/iris.go
+++ b/iris.go
@@ -13,6 +13,7 @@ func main() {
c.JSON(iris.StatusOK, iris.Map{
"Name": "Iris",
"Released": "13 March 2016",
+ "Stars": "5883",
})
})
iris.ListenLETSENCRYPT("mydomain.com")
@@ -27,9 +28,10 @@ import "github.com/kataras/iris"
func main() {
s1 := iris.New()
s1.Get("/hi_json", func(c *iris.Context) {
- c.JSON(200, iris.Map{
+ c.JSON(iris.StatusOK, iris.Map{
"Name": "Iris",
"Released": "13 March 2016",
+ "Stars": "5883",
})
})
@@ -47,7 +49,7 @@ func main() {
For middleware, template engines, response engines, sessions, websockets, mails, subdomains,
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"
@@ -56,6 +58,7 @@ import (
"fmt"
"log"
"net"
+ "net/http"
"net/url"
"os"
"os/signal"
@@ -72,15 +75,13 @@ import (
"github.com/kataras/go-sessions"
"github.com/kataras/go-template"
"github.com/kataras/go-template/html"
- "github.com/kataras/iris/utils"
- "github.com/valyala/fasthttp"
)
const (
// IsLongTermSupport flag is true when the below version number is a long-term-support version
IsLongTermSupport = false
// Version is the current version number of the Iris web framework
- Version = "5.1.3"
+ Version = "6.0.0"
banner = ` _____ _
|_ _| (_)
@@ -96,7 +97,7 @@ var (
Config *Configuration
Logger *log.Logger // if you want colors in your console then you should use this https://github.com/iris-contrib/logger instead.
Plugins PluginContainer
- Router fasthttp.RequestHandler
+ Router http.Handler
Websocket *WebsocketServer
// 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.
@@ -149,7 +150,7 @@ type (
ListenUNIX(string, os.FileMode)
Close() error
Reserve() error
- AcquireCtx(*fasthttp.RequestCtx) *Context
+ AcquireCtx(http.ResponseWriter, *http.Request) *Context
ReleaseCtx(*Context)
CheckForUpdates(bool)
UseSessionDB(sessions.Database)
@@ -168,35 +169,81 @@ type (
Cache(HandlerFunc, time.Duration) HandlerFunc
}
- // Framework is our God |\| Google.Search('Greek mythology Iris')
- //
- // Implements the FrameworkAPI
- 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
- 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
+ // MuxAPI the visible api for the serveMux
+ MuxAPI interface {
+ Party(string, ...HandlerFunc) MuxAPI
+ // middleware serial, appending
+ Use(...Handler) MuxAPI
+ UseFunc(...HandlerFunc) MuxAPI
+ Done(...Handler) MuxAPI
+ DoneFunc(...HandlerFunc) MuxAPI
- contextPool sync.Pool
- once sync.Once
- Config *Configuration
- sessions sessions.Sessions
- serializers serializer.Serializers
- templates *templateEngines
- Logger *log.Logger
- Plugins PluginContainer
- Websocket *WebsocketServer
+ // main handlers
+ Handle(string, string, ...Handler) RouteNameFunc
+ 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
+ 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{}
// New creates and returns a new Iris instance.
@@ -230,7 +277,9 @@ func New(setters ...OptionSetter) *Framework {
// 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
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
func (s *Framework) Must(err error) {
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
// 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() {
Default.Build()
}
@@ -345,14 +395,8 @@ func (s *Framework) Build() {
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
s.mux.setCorrectPath(!s.Config.DisablePathCorrection)
- s.mux.setEscapePath(!s.Config.DisablePathEscape)
s.mux.setFireMethodNotAllowed(s.Config.FireMethodNotAllowed)
// prepare the server's handler, we do that check because iris supports
@@ -360,12 +404,12 @@ func (s *Framework) Build() {
if s.Router == nil {
// build and get the default mux' handler(*Context)
serve := s.mux.BuildHandler()
- // build the fasthttp handler to bind it to the servers
- defaultHandler := func(reqCtx *fasthttp.RequestCtx) {
- ctx := s.AcquireCtx(reqCtx)
+ // build the net/http.Handler to bind it to the servers
+ defaultHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ ctx := s.AcquireCtx(w, r)
serve(ctx)
s.ReleaseCtx(ctx)
- }
+ })
s.Router = defaultHandler
}
@@ -375,19 +419,20 @@ func (s *Framework) Build() {
if s.ln != nil { // user called Listen functions or Serve,
// create the main server
- srvName := "iris"
- if len(DefaultServerName) > 0 {
- srvName += "_" + DefaultServerName
+ s.srv = &http.Server{
+ ReadTimeout: s.Config.ReadTimeout,
+ 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,
- MaxRequestBodySize: s.Config.MaxRequestBodySize,
- ReadBufferSize: s.Config.ReadBufferSize,
- WriteBufferSize: s.Config.WriteBufferSize,
- ReadTimeout: s.Config.ReadTimeout,
- WriteTimeout: s.Config.WriteTimeout,
- MaxConnsPerIP: s.Config.MaxConnsPerIP,
- MaxRequestsPerConn: s.Config.MaxRequestsPerConn,
- Handler: s.Router,
+ if s.Config.TLSNextProto != nil {
+ s.srv.TLSNextProto = s.Config.TLSNextProto
+ }
+ if s.Config.ConnState != nil {
+ s.srv.ConnState = s.Config.ConnState
}
}
@@ -435,9 +480,8 @@ func (s *Framework) Serve(ln net.Listener) error {
s.Logger.Panic(err)
}
}()
-
// 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 {
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 {
s.Logger.Panic(err)
}
-
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() != ""
}
-// 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
// if you want to panic on this error use the iris.Must(iris.Close())
func Close() error {
@@ -663,15 +680,16 @@ func (s *Framework) Reserve() error {
// AcquireCtx gets an Iris' Context from pool
// see .ReleaseCtx & .Serve
-func AcquireCtx(reqCtx *fasthttp.RequestCtx) *Context {
- return Default.AcquireCtx(reqCtx)
+func AcquireCtx(w http.ResponseWriter, r *http.Request) *Context {
+ return Default.AcquireCtx(w, r)
}
// AcquireCtx gets an Iris' Context from pool
// 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.RequestCtx = reqCtx
+ ctx.ResponseWriter = acquireResponseWriter(w)
+ ctx.Request = r
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
// see .AcquireCtx & .Serve
func (s *Framework) ReleaseCtx(ctx *Context) {
+ // flush the body when all finished
+ ctx.ResponseWriter.flushResponse()
+
ctx.Middleware = nil
ctx.session = nil
+ ctx.Request = nil
+ releaseResponseWriter(ctx.ResponseWriter)
+ ctx.values.Reset()
+
s.contextPool.Put(ctx)
}
@@ -978,37 +1003,35 @@ func (s *Framework) Path(routeName string, args ...interface{}) string {
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
// use it only for special cases, when the default behavior doesn't suits you.
//
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
// it uses just the url.QueryUnescape
-func DecodeURL(uri string) string {
- if uri == "" {
+func DecodeQuery(path string) string {
+ if path == "" {
return ""
}
- encodedPath, _ := url.QueryUnescape(uri)
+ encodedPath, err := url.QueryUnescape(path)
+ if err != nil {
+ return path
+ }
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
// use it only for special cases, when the default behavior doesn't suits you.
//
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
-/* Credits to Manish Singh @kryptodev for URLDecode by post issue share code */
-// simple things, if DecodeURL doesn't gives you the results you waited, use this function
-// 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
-func DecodeFasthttpURL(path string) string {
- if path == "" {
+// it uses just the url.Parse
+func DecodeURL(uri string) string {
+ u, err := url.Parse(uri)
+ if err != nil {
return ""
}
- u := fasthttp.AcquireURI()
- u.SetPath(path)
- encodedPath := u.String()[8:]
- fasthttp.ReleaseURI(u)
- return encodedPath
+ return u.String()
}
// 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------------------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
-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
- UseTransaction(...TransactionFunc) MuxAPI
- DoneTransaction(...TransactionFunc) MuxAPI
-
- // main handlers
- Handle(string, string, ...Handler) RouteNameFunc
- 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
- }
-)
+type muxAPI struct {
+ mux *serveMux
+ doneMiddleware Middleware
+ apiRoutes []*route // used to register the .Done middleware
+ relativePath string
+ middleware Middleware
+}
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
// and https://github.com/kataras/iris/blob/master/context_test.go for more
-func UseTransaction(pipes ...TransactionFunc) MuxAPI {
- return Default.UseTransaction(pipes...)
-}
+// func UseTransaction(pipes ...TransactionFunc) MuxAPI {
+// return Default.UseTransaction(pipes...)
+// }
// UseTransaction adds transaction(s) middleware
// 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
// and https://github.com/kataras/iris/blob/master/context_test.go for more
-func (api *muxAPI) UseTransaction(pipes ...TransactionFunc) MuxAPI {
- return api.UseFunc(func(ctx *Context) {
- for i := range pipes {
- ctx.BeginTransaction(pipes[i])
- if ctx.TransactionsSkipped() {
- ctx.StopExecution()
- }
- }
- ctx.Next()
- })
-}
+// func (api *muxAPI) UseTransaction(pipes ...TransactionFunc) MuxAPI {
+// return api.UseFunc(func(ctx *Context) {
+// for i := range pipes {
+// ctx.BeginTransaction(pipes[i])
+// if ctx.TransactionsSkipped() {
+// ctx.StopExecution()
+// }
+// }
+// ctx.Next()
+// })
+// }
// DoneTransaction registers Transaction 'middleware' the only difference from .UseTransaction is that
// 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
// and https://github.com/kataras/iris/blob/master/context_test.go for more
-func DoneTransaction(pipes ...TransactionFunc) MuxAPI {
- return Default.DoneTransaction(pipes...)
-}
+// func DoneTransaction(pipes ...TransactionFunc) MuxAPI {
+// return Default.DoneTransaction(pipes...)
+// }
// DoneTransaction registers Transaction 'middleware' the only difference from .UseTransaction is that
// 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
// and https://github.com/kataras/iris/blob/master/context_test.go for more
-func (api *muxAPI) DoneTransaction(pipes ...TransactionFunc) MuxAPI {
- return api.DoneFunc(func(ctx *Context) {
- for i := range pipes {
- ctx.BeginTransaction(pipes[i])
- }
- })
-}
+// func (api *muxAPI) DoneTransaction(pipes ...TransactionFunc) MuxAPI {
+// return api.DoneFunc(func(ctx *Context) {
+// for i := range pipes {
+// ctx.BeginTransaction(pipes[i])
+// }
+// })
+// }
// 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
@@ -1412,7 +1383,7 @@ func (api *muxAPI) Handle(method string, registedPath string, handlers ...Handle
if len(api.doneMiddleware) > 0 {
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)
// 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
j := 1
- ctx.VisitUserValues(func(k []byte, v interface{}) {
+ ctx.VisitValues(func(k []byte, v interface{}) {
if bytes.HasPrefix(k, paramPrefix) {
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 empty then returns /*wildcard too
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)
}
-// 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
// it's the simpliest form of the Static* functions
// 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)
}
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)
return
}
- ctx.Response.Header.Set(contentType, cType)
- ctx.Response.Header.Set(lastModified, modtime)
+ ctx.ResponseWriter.Header().Set(contentType, cType)
+ ctx.ResponseWriter.Header().Set(lastModified, modtime)
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.
@@ -2127,6 +1886,91 @@ func (api *muxAPI) Favicon(favPath string, requestPath ...string) RouteNameFunc
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
// returns this Party, to continue as normal
// 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
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)
return
}
diff --git a/iris/fs.go b/iris/fs.go
index 91ea0cf7..138d9754 100644
--- a/iris/fs.go
+++ b/iris/fs.go
@@ -1,10 +1,11 @@
package main
import (
- "github.com/kataras/go-errors"
"os"
"path/filepath"
"strings"
+
+ "github.com/kataras/go-errors"
)
var goPath string
diff --git a/iris/get.go b/iris/get.go
index 7b67e77a..7c978e52 100644
--- a/iris/get.go
+++ b/iris/get.go
@@ -2,12 +2,13 @@ package main // #nosec
import (
"fmt"
- "github.com/kataras/cli"
- "github.com/kataras/go-fs"
- "github.com/skratchdot/open-golang/open"
"os"
"os/exec"
"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
diff --git a/iris/run.go b/iris/run.go
index 91afaf97..96522406 100644
--- a/iris/run.go
+++ b/iris/run.go
@@ -2,7 +2,6 @@ package main
import (
"os"
-
"strings"
"github.com/kataras/cli"
diff --git a/plugin.go b/plugin.go
index f806ea0b..dab910d6 100644
--- a/plugin.go
+++ b/plugin.go
@@ -1,9 +1,8 @@
package iris
import (
- "sync"
-
"log"
+ "sync"
"github.com/kataras/go-errors"
"github.com/kataras/go-fs"
diff --git a/plugin_test.go b/plugin_test.go
index 96ef92a2..3cb69164 100644
--- a/plugin_test.go
+++ b/plugin_test.go
@@ -1,12 +1,6 @@
// Black-box Testing
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 (
"fmt"
"github.com/kataras/iris"
diff --git a/response_writer.go b/response_writer.go
new file mode 100644
index 00000000..5d0c80ae
--- /dev/null
+++ b/response_writer.go
@@ -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)
+ }
+}
diff --git a/template.go b/template.go
index 1f3a3ab7..278ead32 100644
--- a/template.go
+++ b/template.go
@@ -1,9 +1,10 @@
package iris
import (
+ "io"
+
"github.com/kataras/go-fs"
"github.com/kataras/go-template"
- "io"
)
const (
@@ -105,14 +106,14 @@ func (t *templateEngines) render(isFile bool, ctx *Context, filenameOrSource str
var out io.Writer
if gzipEnabled && ctx.clientAllowsGzip() {
- ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
+ ctx.ResponseWriter.Header().Add(varyHeader, acceptEncodingHeader)
ctx.SetHeader(contentEncodingHeader, "gzip")
- gzipWriter := fs.AcquireGzipWriter(ctx.Response.BodyWriter())
+ gzipWriter := fs.AcquireGzipWriter(ctx.ResponseWriter)
defer fs.ReleaseGzipWriter(gzipWriter)
out = gzipWriter
} else {
- out = ctx.Response.BodyWriter()
+ out = ctx.ResponseWriter
}
if isFile {
diff --git a/transactions.go b/transactions.go
new file mode 100644
index 00000000..a21c3627
--- /dev/null
+++ b/transactions.go
@@ -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
+})
diff --git a/webfs.go b/webfs.go
new file mode 100644
index 00000000..4f272d48
--- /dev/null
+++ b/webfs.go
@@ -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
+}
diff --git a/websocket.go b/websocket.go
index 9b7d62fa..5f942e5b 100644
--- a/websocket.go
+++ b/websocket.go
@@ -1,7 +1,9 @@
package iris
import (
- irisWebsocket "github.com/iris-contrib/websocket"
+ "net/http"
+ "sync"
+
"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
WebsocketServer struct {
websocket.Server
- upgrader irisWebsocket.Upgrader
-
- // the only fields we need at runtime here for iris-specific error and check origin funcs
- // they comes from WebsocketConfiguration
-
- // Error specifies the function for generating HTTP error responses.
- 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
+ station *Framework
+ once sync.Once
+ // Config:
+ // 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.
+ Config WebsocketConfiguration
}
)
-// NewWebsocketServer returns an empty WebsocketServer, nothing special here.
-func NewWebsocketServer() *WebsocketServer {
- return &WebsocketServer{}
+// NewWebsocketServer returns a new empty unitialized websocket server
+// it runs on first OnConnection
+func NewWebsocketServer(station *Framework) *WebsocketServer {
+ return &WebsocketServer{station: station, Server: websocket.New()}
}
-// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
-//
-// The responseHeader is included in the response to the client's upgrade
-// request. Use the responseHeader to specify cookies (Set-Cookie) and the
-// application negotiated subprotocol (Sec-Websocket-Protocol).
-//
-// 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)
-}
+// NewWebsocketServer creates the client side source route and the route path Endpoint with the correct Handler
+// receives the websocket configuration and the iris station
+// and returns the websocket server which can be attached to more than one iris station (if needed)
+func (ws *WebsocketServer) init() {
+ if ws.Config.Endpoint == "" {
+ ws.Config = ws.station.Config.Websocket
+ }
-// Handler is the iris Handler to upgrade the request
-// used inside RegisterRoutes
-func (s *WebsocketServer) Handler(ctx *Context) {
- // first, check origin
- if !s.CheckOrigin(ctx) {
- s.Error(ctx, StatusForbidden, "websocket: origin not allowed")
+ c := ws.Config
+
+ if c.Endpoint == "" {
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)
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
- if station.Lookup(clientSideLookupName) == nil {
+ if ws.station.Lookup(clientSideLookupName) == nil {
// 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,
PongTimeout: c.PongTimeout,
PingPeriod: c.PingPeriod,
@@ -108,22 +77,13 @@ func (s *WebsocketServer) RegisterTo(station *Framework, c WebsocketConfiguratio
BinaryMessages: c.BinaryMessages,
ReadBufferSize: c.ReadBufferSize,
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
@@ -132,15 +92,10 @@ type WebsocketConnection interface {
}
// OnConnection this is the main event you, as developer, will work with each of the websocket connections
-func (s *WebsocketServer) OnConnection(connectionListener func(WebsocketConnection)) {
- if s.Server == nil {
- // 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.
- 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) {
+func (ws *WebsocketServer) OnConnection(connectionListener func(WebsocketConnection)) {
+ ws.once.Do(ws.init)
+
+ ws.Server.OnConnection(func(c websocket.Connection) {
connectionListener(c)
})
}