mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
Happy new year! Update to 6.0.0 | HTTP/2 full support. https://github.com/kataras/iris/issues/565
full commit from development branch. Examples, book, middleware, plugins are updated to the latest iris version. Read HISTORY.md for more. The 'old' v5 branch which relied on fasthttp exists for those who want to use it navigate there: https://github.com/kataras/iris/tree/5.0.0
This commit is contained in:
parent
ced2083ab3
commit
8bbd9f8fc5
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
|
@ -1,4 +1,6 @@
|
|||
##### Note that I do not accept pull requests and that I use the issue tracker for bug reports and proposals only. Please ask questions on the [https://kataras.rocket.chat/channel/iris][Chat] or [http://stackoverflow.com/](http://stackoverflow.com).
|
||||
If you wanna contribute please submit a PR to one of the [iris-contrib organisation's](https://github.com/iris-contrib) projects.
|
||||
|
||||
##### Note that I do not accept pull requests here and that I use the issue tracker for bug reports and proposals only. Please ask questions on the [https://kataras.rocket.chat/channel/iris][Chat] or [http://stackoverflow.com/](http://stackoverflow.com).
|
||||
|
||||
## Before Submitting an Issue
|
||||
|
||||
|
|
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,4 +1,4 @@
|
|||
- Version : **5.0.4**
|
||||
- Version : **6.0.0**
|
||||
|
||||
- Operating System:
|
||||
|
||||
|
|
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -1,5 +1 @@
|
|||
If you are interested in contributing to the Iris project, please see the document [CONTRIBUTING](https://github.com/kataras/iris/blob/master/.github/CONTRIBUTING.md).
|
||||
|
||||
##### Note that I do not accept pull requests and that I use the issue tracker for bug reports and proposals only. Please ask questions on the [https://kataras.rocket.chat/channel/iris][Chat] or [http://stackoverflow.com/](http://stackoverflow.com).
|
||||
|
||||
[Chat]: https://kataras.rocket.chat/channel/iris
|
||||
|
|
27
DONATIONS.md
27
DONATIONS.md
|
@ -1,11 +1,6 @@
|
|||
|
||||
I spend all my time in the construction of Iris, therefore I have no income value.
|
||||
|
||||
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**
|
||||
|
|
52
HISTORY.md
52
HISTORY.md
|
@ -2,6 +2,58 @@
|
|||
|
||||
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
|
||||
|
||||
|
||||
## 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*
|
||||
|
||||
|
|
44
LICENSE
44
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.
|
||||
|
|
247
README.md
247
README.md
|
@ -20,7 +20,7 @@
|
|||
<br/>
|
||||
|
||||
|
||||
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%205.1.3%20-blue.svg?style=flat-square" alt="Releases"></a>
|
||||
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%206.0.0%20-blue.svg?style=flat-square" alt="Releases"></a>
|
||||
|
||||
<a href="https://github.com/iris-contrib/examples"><img src="https://img.shields.io/badge/%20examples-repository-3362c2.svg?style=flat-square" alt="Examples"></a>
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
|||
<a href="https://kataras.rocket.chat/channel/iris"><img src="https://img.shields.io/badge/%20community-chat-00BCD4.svg?style=flat-square" alt="Chat"></a><br/>
|
||||
<br/>
|
||||
|
||||
<b>Iris</b> is the fastest back-end web framework written in Go.
|
||||
<b>Iris</b> is the fastest HTTP/2 web framework written in Go.
|
||||
<br/>
|
||||
<b>Easy</b> to <a href="https://docs.iris-go.com">learn</a> while it's highly customizable,
|
||||
ideally suited for <br/> both experienced and novice developers.<br/><br/>
|
||||
|
@ -39,8 +39,6 @@ Besides the fact that Iris is faster than any alternatives you may met before, <
|
|||
If you're coming from <a href="https://nodejs.org/en/">Node.js</a> world, this is the <a href="https://github.com/expressjs/express">expressjs</a> alternative for the <a href="https://golang.org">Go Programming Language.</a>
|
||||
<br/>
|
||||
|
||||
<br/>
|
||||
<img src="https://raw.githubusercontent.com/smallnest/go-web-framework-benchmark/4db507a22c964c9bc9774c5b31afdc199a0fe8b7/benchmark.png" alt="Benchmark Wizzard July 21, 2016- Processing Time Horizontal Graph" />
|
||||
</p>
|
||||
|
||||
|
||||
|
@ -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!
|
||||
|
||||
<p>
|
||||
|
||||
<img width="535" align="left" src="https://github.com/iris-contrib/website/raw/gh-pages/assets/chtree.jpg" />
|
||||
|
||||
This project started ~9 months ago<br/>
|
||||
and rapidly won your trust,<br/>
|
||||
I'm very thankful for this and<br/>
|
||||
I promise you that I'll continue to<br/>
|
||||
do my bests.<br/><br/>
|
||||
|
||||
All people, poor or rich, should give<br/>
|
||||
and share with each others.
|
||||
<br/><br/>
|
||||
As a person who knows<br/>
|
||||
how someone feels when opens<br/>
|
||||
the fridge and finds nothing to eat, again, <br/>
|
||||
I decided that all the <a href="https://github.com/kataras/iris/blob/master/DONATIONS.md">money you<br/>
|
||||
donated so far[<i>424 EUR</i>]</a>,<br/>
|
||||
and until the end of this month,<br/>
|
||||
<b>should go back to the people<br/>
|
||||
who need them most.</b><br/>
|
||||
Is not enough but...
|
||||
|
||||
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
CHRISTMAS IS MOST TRULY<br/>
|
||||
CHRISTMAS WHEN WE<br/>
|
||||
CELEBRATE IT BY GIVING THE<br/>
|
||||
LIGHT OF LOVE TO THOSE<br/>
|
||||
WHO NEED IT MOST.
|
||||
<br/><br/>
|
||||
~ Ruth Carter Stapleton
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
</p>
|
||||
|
||||
### New
|
||||
|
||||
|
@ -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
|
||||
|
|
228
configuration.go
228
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,
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
package iris_test
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
// go test -v -run TestConfig*
|
||||
|
|
1138
context.go
1138
context.go
File diff suppressed because it is too large
Load Diff
410
context_test.go
410
context_test.go
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"encoding/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 := "<h2>Template: %s\nIP: %s</h2><b>%s</b>"
|
||||
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 := "<h2>Template: %s</h2><b>%s</b>"
|
||||
expctedErrMsg := fmt.Sprintf(errTmpl, file, "Error: Unable to execute a template. Trace: Templates are disabled '.Config.DisableTemplatesEngines = true' please turn that to false, as defaulted.\n")
|
||||
|
||||
iris.Get("/renderErr", func(ctx *iris.Context) {
|
||||
ctx.MustRender(file, nil)
|
||||
|
@ -788,46 +662,36 @@ func TestTransactions(t *testing.T) {
|
|||
secondTransactionSuccessHTMLMessage := "<h1>This will sent at all cases because it lives on different transaction and it doesn't fails</h1>"
|
||||
persistMessage := "<h1>I persist show this message to the client!</h1>"
|
||||
|
||||
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 := "<h1>custom error</h1>"
|
||||
iris.OnError(iris.StatusInternalServerError, func(ctx *iris.Context) {
|
||||
ctx.Text(iris.StatusInternalServerError, customErrorTemplateText)
|
||||
})
|
||||
|
||||
failureWithRegisteredErrorHandler := func(ctx *iris.Context) {
|
||||
ctx.BeginTransaction(func(transaction *iris.Transaction) {
|
||||
transaction.SetScope(iris.RequestTransactionScope)
|
||||
err := iris.NewTransactionErrResult()
|
||||
err.StatusCode = iris.StatusInternalServerError // set only the status code in order to execute the registered template
|
||||
transaction.Complete(err)
|
||||
})
|
||||
|
||||
ctx.Text(iris.StatusOK, "this will not be sent to the client because first is requested scope and it's failed")
|
||||
}
|
||||
|
||||
iris.Get("/failAllBecauseFirstTransactionFailedWithRegisteredErrorTemplate", failureWithRegisteredErrorHandler)
|
||||
|
||||
e := httptest.New(iris.Default, t)
|
||||
|
||||
e.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 := "<b>Transaction here</b>"
|
||||
expectedResponse := transactionHTMLResponse + allowMsg
|
||||
api.UseTransaction(func(scope *iris.TransactionScope) {
|
||||
scope.Context.HTML(iris.StatusOK, transactionHTMLResponse)
|
||||
// scope.Context.Next() is automatically called on UseTransaction
|
||||
})
|
||||
|
||||
api.Get("/route1", func(ctx *iris.Context) {
|
||||
ctx.Write(allowMsg)
|
||||
})
|
||||
|
||||
e = httptest.New(api, t)
|
||||
e.GET("/route1").
|
||||
Expect().
|
||||
Status(iris.StatusOK).
|
||||
ContentType("text/html", api.Config.Charset).
|
||||
Body().
|
||||
Equal(expectedResponse)
|
||||
}
|
||||
|
||||
func TestTransactionFailureCompletionButSilently(t *testing.T) {
|
||||
iris.ResetDefault()
|
||||
expectedBody := "I don't care for any unexpected panics, this response should be sent."
|
||||
|
||||
iris.Get("/panic_silent", func(ctx *iris.Context) {
|
||||
ctx.BeginTransaction(func(scope *iris.TransactionScope) {
|
||||
scope.Context.Write("blablabla this should not be shown because of 'unexpected' panic.")
|
||||
panic("OMG, UNEXPECTED ERROR BECAUSE YOU ARE NOT A DISCIPLINED PROGRAMMER, BUT IRIS HAS YOU COVERED!")
|
||||
})
|
||||
|
||||
ctx.WriteString(expectedBody)
|
||||
})
|
||||
|
||||
iris.Get("/expected_error_but_silent_instead_of_send_the_reason", func(ctx *iris.Context) {
|
||||
ctx.BeginTransaction(func(scope *iris.TransactionScope) {
|
||||
scope.Context.Write("this will not be sent.")
|
||||
// complete with a failure ( so revert the changes) but do it silently.
|
||||
scope.Complete(iris.NewErrFallback())
|
||||
})
|
||||
|
||||
ctx.WriteString(expectedBody)
|
||||
})
|
||||
|
||||
iris.Get("/silly_way_expected_error_but_silent_instead_of_send_the_reason", func(ctx *iris.Context) {
|
||||
ctx.BeginTransaction(func(scope *iris.TransactionScope) {
|
||||
scope.Context.Write("this will not be sent.")
|
||||
|
||||
// or if you know the error will be silent from the beggining: err := &iris.ErrFallback{}
|
||||
err := iris.NewErrWithStatus()
|
||||
|
||||
fail := true
|
||||
|
||||
if fail {
|
||||
err.Status(iris.StatusBadRequest).Reason("we dont know but it was expected error")
|
||||
}
|
||||
|
||||
// we change our mind we don't want to send the error to the user, so err.Silent to the .Complete
|
||||
// complete with a failure ( so revert the changes) but do it silently.
|
||||
scope.Complete(err.Silent())
|
||||
})
|
||||
|
||||
ctx.WriteString(expectedBody)
|
||||
})
|
||||
|
||||
e := httptest.New(iris.Default, t)
|
||||
|
||||
e.GET("/panic_silent").Expect().
|
||||
Status(iris.StatusOK).
|
||||
Body().
|
||||
Equal(expectedBody)
|
||||
|
||||
e.GET("/expected_error_but_silent_instead_of_send_the_reason").
|
||||
Expect().
|
||||
Status(iris.StatusOK).
|
||||
Body().
|
||||
Equal(expectedBody)
|
||||
|
||||
e.GET("/silly_way_expected_error_but_silent_instead_of_send_the_reason").
|
||||
Expect().
|
||||
Status(iris.StatusOK).
|
||||
Body().
|
||||
Equal(expectedBody)
|
||||
|
||||
|
||||
e.GET("/failAllBecauseFirstTransactionFailedWithRegisteredErrorTemplate").
|
||||
Expect().
|
||||
Status(iris.StatusInternalServerError).
|
||||
Body().
|
||||
Equal(customErrorTemplateText)
|
||||
}
|
||||
|
|
442
http.go
442
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 := "<a href=\"" + HTMLEscape(urlToRedirect) + "\">Moved Permanently</a>.\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() }
|
||||
}
|
||||
|
|
222
http_test.go
222
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)
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
662
iris.go
662
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
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/go-errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/go-errors"
|
||||
)
|
||||
|
||||
var goPath string
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/cli"
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
package iris
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/go-errors"
|
||||
"github.com/kataras/go-fs"
|
||||
|
|
|
@ -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"
|
||||
|
|
280
response_writer.go
Normal file
280
response_writer.go
Normal file
|
@ -0,0 +1,280 @@
|
|||
package iris
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/go-errors"
|
||||
"github.com/kataras/go-fs"
|
||||
"github.com/klauspost/compress/gzip"
|
||||
)
|
||||
|
||||
type gzipResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
gzipWriter *gzip.Writer
|
||||
}
|
||||
|
||||
var gzpool = sync.Pool{New: func() interface{} { return &gzipResponseWriter{} }}
|
||||
|
||||
func acquireGzipResponseWriter(underline http.ResponseWriter) *gzipResponseWriter {
|
||||
w := gzpool.Get().(*gzipResponseWriter)
|
||||
w.ResponseWriter = underline
|
||||
w.gzipWriter = fs.AcquireGzipWriter(w.ResponseWriter)
|
||||
return w
|
||||
}
|
||||
|
||||
func releaseGzipResponseWriter(w *gzipResponseWriter) {
|
||||
fs.ReleaseGzipWriter(w.gzipWriter)
|
||||
gzpool.Put(w)
|
||||
}
|
||||
|
||||
// Write compresses and writes that data to the underline response writer
|
||||
func (w *gzipResponseWriter) Write(contents []byte) (int, error) {
|
||||
return w.gzipWriter.Write(contents)
|
||||
}
|
||||
|
||||
var rpool = sync.Pool{New: func() interface{} { return &ResponseWriter{} }}
|
||||
|
||||
func acquireResponseWriter(underline http.ResponseWriter) *ResponseWriter {
|
||||
w := rpool.Get().(*ResponseWriter)
|
||||
w.ResponseWriter = underline
|
||||
w.headers = underline.Header()
|
||||
return w
|
||||
}
|
||||
|
||||
func releaseResponseWriter(w *ResponseWriter) {
|
||||
w.headers = nil
|
||||
w.ResponseWriter = nil
|
||||
w.statusCode = 0
|
||||
w.beforeFlush = nil
|
||||
w.ResetBody()
|
||||
rpool.Put(w)
|
||||
}
|
||||
|
||||
// A ResponseWriter interface is used by an HTTP handler to
|
||||
// construct an HTTP response.
|
||||
//
|
||||
// A ResponseWriter may not be used after the Handler.ServeHTTP method
|
||||
// has returned.
|
||||
type ResponseWriter struct {
|
||||
// yes only one callback, we need simplicity here because on EmitError the beforeFlush events should NOT be cleared
|
||||
// but the response is cleared.
|
||||
// Sometimes is useful to keep the event,
|
||||
// so we keep one func only and let the user decide when he/she wants to override it with an empty func before the EmitError (context's behavior)
|
||||
beforeFlush func()
|
||||
http.ResponseWriter
|
||||
// these three fields are setted on flushBody which runs only once on the end of the handler execution.
|
||||
// this helps the performance on multi-write and keep tracks the body, status code and headers in order to run each transaction
|
||||
// on its own
|
||||
chunks []byte // keep track of the body in order to be resetable and useful inside custom transactions
|
||||
statusCode int // the saved status code which will be used from the cache service
|
||||
headers http.Header // the saved headers
|
||||
}
|
||||
|
||||
// Header returns the header map that will be sent by
|
||||
// WriteHeader. Changing the header after a call to
|
||||
// WriteHeader (or Write) has no effect unless the modified
|
||||
// headers were declared as trailers by setting the
|
||||
// "Trailer" header before the call to WriteHeader (see example).
|
||||
// To suppress implicit response headers, set their value to nil.
|
||||
func (w *ResponseWriter) Header() http.Header {
|
||||
return w.headers
|
||||
}
|
||||
|
||||
// StatusCode returns the status code header value
|
||||
func (w *ResponseWriter) StatusCode() int {
|
||||
return w.statusCode
|
||||
}
|
||||
|
||||
// Adds the contents to the body reply, it writes the contents temporarily
|
||||
// to a value in order to be flushed at the end of the request,
|
||||
// this method give us the opportunity to reset the body if needed.
|
||||
//
|
||||
// If WriteHeader has not yet been called, Write calls
|
||||
// WriteHeader(http.StatusOK) before writing the data. If the Header
|
||||
// does not contain a Content-Type line, Write adds a Content-Type set
|
||||
// to the result of passing the initial 512 bytes of written data to
|
||||
// DetectContentType.
|
||||
//
|
||||
// Depending on the HTTP protocol version and the client, calling
|
||||
// Write or WriteHeader may prevent future reads on the
|
||||
// Request.Body. For HTTP/1.x requests, handlers should read any
|
||||
// needed request body data before writing the response. Once the
|
||||
// headers have been flushed (due to either an explicit Flusher.Flush
|
||||
// call or writing enough data to trigger a flush), the request body
|
||||
// may be unavailable. For HTTP/2 requests, the Go HTTP server permits
|
||||
// handlers to continue to read the request body while concurrently
|
||||
// writing the response. However, such behavior may not be supported
|
||||
// by all HTTP/2 clients. Handlers should read before writing if
|
||||
// possible to maximize compatibility.
|
||||
func (w *ResponseWriter) Write(contents []byte) (int, error) {
|
||||
w.chunks = append(w.chunks, contents...)
|
||||
return len(w.chunks), nil
|
||||
}
|
||||
|
||||
// Body returns the body tracked from the writer so far
|
||||
// do not use this for edit.
|
||||
func (w *ResponseWriter) Body() []byte {
|
||||
return w.chunks
|
||||
}
|
||||
|
||||
// SetBodyString overrides the body and sets it to a string value
|
||||
func (w *ResponseWriter) SetBodyString(s string) {
|
||||
w.chunks = []byte(s)
|
||||
}
|
||||
|
||||
// SetBody overrides the body and sets it to a slice of bytes value
|
||||
func (w *ResponseWriter) SetBody(b []byte) {
|
||||
w.chunks = b
|
||||
}
|
||||
|
||||
// ResetBody resets the response body
|
||||
func (w *ResponseWriter) ResetBody() {
|
||||
w.chunks = w.chunks[0:0]
|
||||
}
|
||||
|
||||
// ResetHeaders clears the temp headers
|
||||
func (w *ResponseWriter) ResetHeaders() {
|
||||
// original response writer's headers are empty.
|
||||
w.headers = w.ResponseWriter.Header()
|
||||
}
|
||||
|
||||
// Reset resets the response body, headers and the status code header
|
||||
func (w *ResponseWriter) Reset() {
|
||||
w.ResetHeaders()
|
||||
w.statusCode = 0
|
||||
w.ResetBody()
|
||||
}
|
||||
|
||||
// WriteHeader sends an HTTP response header with status code.
|
||||
// If WriteHeader is not called explicitly, the first call to Write
|
||||
// will trigger an implicit WriteHeader(http.StatusOK).
|
||||
// Thus explicit calls to WriteHeader are mainly used to
|
||||
// send error codes.
|
||||
func (w *ResponseWriter) WriteHeader(statusCode int) {
|
||||
w.statusCode = statusCode
|
||||
}
|
||||
|
||||
// ContentType returns the content type, if not setted returns empty string
|
||||
func (w *ResponseWriter) ContentType() string {
|
||||
return w.headers.Get(contentType)
|
||||
}
|
||||
|
||||
// SetContentType sets the content type header
|
||||
func (w *ResponseWriter) SetContentType(cType string) {
|
||||
w.headers.Set(contentType, cType)
|
||||
}
|
||||
|
||||
var errHijackNotSupported = errors.New("Hijack is not supported to this response writer!")
|
||||
|
||||
// Hijack lets the caller take over the connection.
|
||||
// After a call to Hijack(), the HTTP server library
|
||||
// will not do anything else with the connection.
|
||||
//
|
||||
// It becomes the caller's responsibility to manage
|
||||
// and close the connection.
|
||||
//
|
||||
// The returned net.Conn may have read or write deadlines
|
||||
// already set, depending on the configuration of the
|
||||
// Server. It is the caller's responsibility to set
|
||||
// or clear those deadlines as needed.
|
||||
func (w *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if h, isHijacker := w.ResponseWriter.(http.Hijacker); isHijacker {
|
||||
return h.Hijack()
|
||||
}
|
||||
|
||||
return nil, nil, errHijackNotSupported
|
||||
}
|
||||
|
||||
// SetBeforeFlush registers the unique callback which called exactly before the response is flushed to the client
|
||||
func (w *ResponseWriter) SetBeforeFlush(cb func()) {
|
||||
w.beforeFlush = cb
|
||||
}
|
||||
|
||||
// flushResponse the full body, headers and status code to the underline response writer
|
||||
// called automatically at the end of each request, see ReleaseCtx
|
||||
func (w *ResponseWriter) flushResponse() {
|
||||
|
||||
if w.beforeFlush != nil {
|
||||
w.beforeFlush()
|
||||
}
|
||||
|
||||
if w.statusCode > 0 {
|
||||
w.ResponseWriter.WriteHeader(w.statusCode)
|
||||
}
|
||||
|
||||
if w.headers != nil {
|
||||
for k, values := range w.headers {
|
||||
for i := range values {
|
||||
w.ResponseWriter.Header().Add(k, values[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(w.chunks) > 0 {
|
||||
w.ResponseWriter.Write(w.chunks)
|
||||
}
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (w *ResponseWriter) Flush() {
|
||||
w.flushResponse()
|
||||
// The Flusher interface is implemented by ResponseWriters that allow
|
||||
// an HTTP handler to flush buffered data to the client.
|
||||
//
|
||||
// The default HTTP/1.x and HTTP/2 ResponseWriter implementations
|
||||
// support Flusher, but ResponseWriter wrappers may not. Handlers
|
||||
// should always test for this ability at runtime.
|
||||
//
|
||||
// Note that even for ResponseWriters that support Flush,
|
||||
// if the client is connected through an HTTP proxy,
|
||||
// the buffered data may not reach the client until the response
|
||||
// completes.
|
||||
if fl, isFlusher := w.ResponseWriter.(http.Flusher); isFlusher {
|
||||
fl.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// clone returns a clone of this response writer
|
||||
// it copies the header, status code, headers and the beforeFlush finally returns a new ResponseWriter
|
||||
func (w *ResponseWriter) clone() *ResponseWriter {
|
||||
wc := &ResponseWriter{}
|
||||
wc.ResponseWriter = w.ResponseWriter
|
||||
wc.statusCode = w.statusCode
|
||||
wc.headers = w.headers
|
||||
wc.chunks = w.chunks[0:]
|
||||
wc.beforeFlush = w.beforeFlush
|
||||
return wc
|
||||
}
|
||||
|
||||
// writeTo writes a response writer (temp: status code, headers and body) to another response writer
|
||||
func (w *ResponseWriter) writeTo(to *ResponseWriter) {
|
||||
// set the status code, failure status code are first class
|
||||
if w.statusCode > 0 {
|
||||
to.statusCode = w.statusCode
|
||||
}
|
||||
|
||||
// append the headers
|
||||
if w.headers != nil {
|
||||
for k, values := range w.headers {
|
||||
for _, v := range values {
|
||||
if to.headers.Get(v) == "" {
|
||||
to.headers.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// append the body
|
||||
if len(w.chunks) > 0 {
|
||||
to.Write(w.chunks)
|
||||
}
|
||||
|
||||
if w.beforeFlush != nil {
|
||||
to.SetBeforeFlush(w.beforeFlush)
|
||||
}
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
package iris
|
||||
|
||||
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 {
|
||||
|
|
166
transactions.go
Normal file
166
transactions.go
Normal file
|
@ -0,0 +1,166 @@
|
|||
package iris
|
||||
|
||||
// TransactionErrResult could be named also something like 'MaybeError',
|
||||
// it is useful to send it on transaction.Complete in order to execute a custom error mesasge to the user.
|
||||
//
|
||||
// in simple words it's just a 'traveler message' between the transaction and its scope.
|
||||
// it is totally optional
|
||||
type TransactionErrResult struct {
|
||||
StatusCode int
|
||||
// if reason is empty then the already relative registered (custom or not)
|
||||
// error will be executed if the scope allows that.
|
||||
Reason string
|
||||
ContentType string
|
||||
}
|
||||
|
||||
// Error returns the reason given by the user or an empty string
|
||||
func (err TransactionErrResult) Error() string {
|
||||
return err.Reason
|
||||
}
|
||||
|
||||
// IsFailure returns true if this is an actual error
|
||||
func (err TransactionErrResult) IsFailure() bool {
|
||||
return err.StatusCode >= 400
|
||||
}
|
||||
|
||||
// NewTransactionErrResult returns a new transaction result with the given error message,
|
||||
// it can be empty too, but if not then the transaction's scope is decided what to do with that
|
||||
func NewTransactionErrResult() TransactionErrResult {
|
||||
return TransactionErrResult{}
|
||||
}
|
||||
|
||||
// Transaction gives the users the opportunity to code their route handlers cleaner and safier
|
||||
// it receives a scope which is decided when to send an error to the user, recover from panics
|
||||
// stop the execution of the next transactions and so on...
|
||||
//
|
||||
// it's default scope is the TransientTransactionScope which is silently
|
||||
// skips the current transaction's response if transaction.Complete accepts a non-empty error.
|
||||
//
|
||||
// Create and set custom transactions scopes with transaction.SetScope.
|
||||
//
|
||||
// For more information please view the tests
|
||||
type Transaction struct {
|
||||
Context *Context
|
||||
parent *Context
|
||||
hasError bool
|
||||
scope TransactionScope
|
||||
}
|
||||
|
||||
func newTransaction(from *Context) *Transaction {
|
||||
tempCtx := *from
|
||||
writer := tempCtx.ResponseWriter.clone()
|
||||
tempCtx.ResponseWriter = writer
|
||||
t := &Transaction{
|
||||
parent: from,
|
||||
Context: &tempCtx,
|
||||
scope: TransientTransactionScope,
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// SetScope sets the current transaction's scope
|
||||
// iris.RequestTransactionScope || iris.TransientTransactionScope (default)
|
||||
func (t *Transaction) SetScope(scope TransactionScope) {
|
||||
t.scope = scope
|
||||
}
|
||||
|
||||
// Complete completes the transaction
|
||||
// rollback and send an error when the error is not empty.
|
||||
// The next steps depends on its Scope.
|
||||
//
|
||||
// The error can be a type of NewTransactionErrResult()
|
||||
func (t *Transaction) Complete(err error) {
|
||||
maybeErr := TransactionErrResult{}
|
||||
|
||||
if err != nil {
|
||||
t.hasError = true
|
||||
|
||||
statusCode := StatusBadRequest
|
||||
reason := err.Error()
|
||||
cType := "text/plain; charset=" + t.Context.framework.Config.Charset
|
||||
|
||||
if errWstatus, ok := err.(TransactionErrResult); ok {
|
||||
if errWstatus.StatusCode > 0 {
|
||||
statusCode = errWstatus.StatusCode
|
||||
}
|
||||
|
||||
if errWstatus.Reason != "" {
|
||||
reason = errWstatus.Reason
|
||||
}
|
||||
// get the content type used on this transaction
|
||||
if cTypeH := t.Context.ResponseWriter.Header().Get(contentType); cTypeH != "" {
|
||||
cType = cTypeH
|
||||
}
|
||||
|
||||
}
|
||||
maybeErr.StatusCode = statusCode
|
||||
maybeErr.Reason = reason
|
||||
maybeErr.ContentType = cType
|
||||
}
|
||||
// the transaction ends with error or not error, it decides what to do next with its Response
|
||||
// the Response is appended to the parent context an all cases but it checks for empty body,headers and all that,
|
||||
// if they are empty (silent error or not error at all)
|
||||
// then all transaction's actions are skipped as expected
|
||||
canContinue := t.scope.EndTransaction(maybeErr, t.Context)
|
||||
if !canContinue {
|
||||
t.parent.SkipTransactions()
|
||||
}
|
||||
}
|
||||
|
||||
// TransactionScope is the manager of the transaction's response, can be resseted and skipped
|
||||
// from its parent context or execute an error or skip other transactions
|
||||
type TransactionScope interface {
|
||||
// EndTransaction returns if can continue to the next transactions or not (false)
|
||||
// called after Complete, empty or not empty error
|
||||
EndTransaction(maybeErr TransactionErrResult, ctx *Context) bool
|
||||
}
|
||||
|
||||
// TransactionScopeFunc the transaction's scope signature
|
||||
type TransactionScopeFunc func(maybeErr TransactionErrResult, ctx *Context) bool
|
||||
|
||||
// EndTransaction ends the transaction with a callback to itself, implements the TransactionScope interface
|
||||
func (tsf TransactionScopeFunc) EndTransaction(maybeErr TransactionErrResult, ctx *Context) bool {
|
||||
return tsf(maybeErr, ctx)
|
||||
}
|
||||
|
||||
// TransientTransactionScope explaination:
|
||||
//
|
||||
// independent 'silent' scope, if transaction fails (if transaction.IsFailure() == true)
|
||||
// then its response is not written to the real context no error is provided to the user.
|
||||
// useful for the most cases.
|
||||
var TransientTransactionScope = TransactionScopeFunc(func(maybeErr TransactionErrResult, ctx *Context) bool {
|
||||
if maybeErr.IsFailure() {
|
||||
ctx.ResponseWriter.Reset() // this response is skipped because it's empty.
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// RequestTransactionScope explaination:
|
||||
//
|
||||
// if scope fails (if transaction.IsFailure() == true)
|
||||
// then the rest of the context's response (transaction or normal flow)
|
||||
// is not written to the client, and an error status code is written instead.
|
||||
var RequestTransactionScope = TransactionScopeFunc(func(maybeErr TransactionErrResult, ctx *Context) bool {
|
||||
if maybeErr.IsFailure() {
|
||||
// we need to register a beforeResponseFlush event here in order
|
||||
// to execute last the EmitError
|
||||
// (which will reset the whole response's body, status code and headers setted from normal flow or other transactions too)
|
||||
ctx.ResponseWriter.SetBeforeFlush(func() {
|
||||
if maybeErr.Reason != "" {
|
||||
ctx.ResponseWriter.Reset()
|
||||
// send the error with the info user provided
|
||||
ctx.ResponseWriter.SetBodyString(maybeErr.Reason)
|
||||
ctx.ResponseWriter.WriteHeader(maybeErr.StatusCode)
|
||||
ctx.ResponseWriter.SetContentType(maybeErr.ContentType)
|
||||
} else {
|
||||
// else execute the registered user error and skip the next transactions and all normal flow,
|
||||
ctx.EmitError(maybeErr.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
149
webfs.go
Normal file
149
webfs.go
Normal file
|
@ -0,0 +1,149 @@
|
|||
package iris
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// StaticHandlerBuilder is the web file system's Handler builder
|
||||
// use that or the iris.StaticHandler/StaticWeb methods
|
||||
type StaticHandlerBuilder interface {
|
||||
Path(requestRoutePath string) StaticHandlerBuilder
|
||||
Gzip(enable bool) StaticHandlerBuilder
|
||||
Listing(listDirectoriesOnOff bool) StaticHandlerBuilder
|
||||
StripPath(yesNo bool) StaticHandlerBuilder
|
||||
Build() HandlerFunc
|
||||
}
|
||||
|
||||
type webfs struct {
|
||||
// user options, only directory is required.
|
||||
directory http.Dir
|
||||
requestPath string
|
||||
stripPath bool
|
||||
gzip bool
|
||||
listDirectories bool
|
||||
// these are init on the Build() call
|
||||
filesystem http.FileSystem
|
||||
once sync.Once
|
||||
handler HandlerFunc
|
||||
}
|
||||
|
||||
func toWebPath(systemPath string) string {
|
||||
// winos slash to slash
|
||||
webpath := strings.Replace(systemPath, "\\", slash, -1)
|
||||
// double slashes to single
|
||||
webpath = strings.Replace(webpath, slash+slash, slash, -1)
|
||||
// remove all dots
|
||||
webpath = strings.Replace(webpath, ".", "", -1)
|
||||
return webpath
|
||||
}
|
||||
|
||||
// NewStaticHandlerBuilder returns a new Handler which serves static files
|
||||
// supports gzip, no listing and much more
|
||||
// Note that, this static builder returns a Handler
|
||||
// it doesn't cares about the rest of your iris configuration.
|
||||
//
|
||||
// Use the iris.StaticHandler/StaticWeb in order to serve static files on more automatic way
|
||||
// this builder is used by people who have more complicated application
|
||||
// structure and want a fluent api to work on.
|
||||
func NewStaticHandlerBuilder(dir string) StaticHandlerBuilder {
|
||||
return &webfs{
|
||||
directory: http.Dir(dir),
|
||||
// default route path is the same as the directory
|
||||
requestPath: toWebPath(dir),
|
||||
// enable strip path by-default
|
||||
stripPath: true,
|
||||
// gzip is disabled by default
|
||||
gzip: false,
|
||||
// list directories disabled by default
|
||||
listDirectories: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Path sets the request path.
|
||||
// Defaults to same as system path
|
||||
func (w *webfs) Path(requestRoutePath string) StaticHandlerBuilder {
|
||||
w.requestPath = toWebPath(requestRoutePath)
|
||||
return w
|
||||
}
|
||||
|
||||
// Gzip if enable is true then gzip compression is enabled for this static directory
|
||||
// Defaults to false
|
||||
func (w *webfs) Gzip(enable bool) StaticHandlerBuilder {
|
||||
w.gzip = enable
|
||||
return w
|
||||
}
|
||||
|
||||
// Listing turn on/off the 'show files and directories'.
|
||||
// Defaults to false
|
||||
func (w *webfs) Listing(listDirectoriesOnOff bool) StaticHandlerBuilder {
|
||||
w.listDirectories = listDirectoriesOnOff
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *webfs) StripPath(yesNo bool) StaticHandlerBuilder {
|
||||
w.stripPath = yesNo
|
||||
return w
|
||||
}
|
||||
|
||||
type (
|
||||
noListFile struct {
|
||||
http.File
|
||||
}
|
||||
)
|
||||
|
||||
// Overrides the Readdir of the http.File in order to disable showing a list of the dirs/files
|
||||
func (n noListFile) Readdir(count int) ([]os.FileInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Implements the http.Filesystem
|
||||
// Do not call it.
|
||||
func (w *webfs) Open(name string) (http.File, error) {
|
||||
info, err := w.filesystem.Open(name)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !w.listDirectories {
|
||||
return noListFile{info}, nil
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// Build the handler (once) and returns it
|
||||
func (w *webfs) Build() HandlerFunc {
|
||||
// we have to ensure that Build is called ONLY one time,
|
||||
// one instance per one static directory.
|
||||
w.once.Do(func() {
|
||||
w.filesystem = http.Dir(w.directory)
|
||||
|
||||
// set the filesystem to itself in order to be recognised of listing property (can be change at runtime too)
|
||||
fileserver := http.FileServer(w)
|
||||
fsHandler := fileserver
|
||||
if w.stripPath {
|
||||
prefix := w.requestPath
|
||||
fsHandler = http.StripPrefix(prefix, fileserver)
|
||||
}
|
||||
|
||||
w.handler = func(ctx *Context) {
|
||||
writer := ctx.ResponseWriter.ResponseWriter
|
||||
|
||||
if w.gzip && ctx.clientAllowsGzip() {
|
||||
ctx.ResponseWriter.Header().Add(varyHeader, acceptEncodingHeader)
|
||||
ctx.SetHeader(contentEncodingHeader, "gzip")
|
||||
gzipResWriter := acquireGzipResponseWriter(ctx.ResponseWriter.ResponseWriter)
|
||||
writer = gzipResWriter
|
||||
defer releaseGzipResponseWriter(gzipResWriter)
|
||||
}
|
||||
|
||||
fsHandler.ServeHTTP(writer, ctx.Request)
|
||||
}
|
||||
})
|
||||
|
||||
return w.handler
|
||||
}
|
135
websocket.go
135
websocket.go
|
@ -1,7 +1,9 @@
|
|||
package iris
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user