Merge pull request #11 from kataras/master

.

Former-commit-id: 5c2fc434d0dfb1371753db2b229998a397761990
This commit is contained in:
Zeno-Code 2018-03-19 09:58:25 +08:00 committed by GitHub
commit 8973565acb
37 changed files with 20847 additions and 167 deletions

View File

@ -19,4 +19,9 @@ after_script:
# typescript examples
- cd ./typescript/_examples
- go get ./...
- go test -v -cover ./...
- cd ../../
# make sure that the _benchmarks code is working
- cd ./_benchmarks
- go get ./...
- go test -v -cover ./...

2
Gopkg.lock generated
View File

@ -89,7 +89,7 @@
branch = "master"
name = "github.com/iris-contrib/httpexpect"
packages = ["."]
revision = "65e93247e7071782e760f82086e8b677cca0c78a"
revision = "ebe99fcebbcedf6e7916320cce24c3e1832766ac"
[[projects]]
branch = "master"

View File

@ -17,6 +17,33 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris` or let the automatic updater do that for you.
# We, 14 March 2018 | v10.4.0
- fix `APIBuilder, Party#StaticWeb` and `APIBuilder, Party#StaticEmbedded` wrong strip prefix inside children parties
- keep the `iris, core/router#StaticEmbeddedHandler` and remove the `core/router/APIBuilder#StaticEmbeddedHandler`, (note the `Handler` suffix) it's global and has nothing to do with the `Party` or the `APIBuilder`
- fix high path cleaning between `{}` (we already escape those contents at the [interpreter](core/router/macro/interpreter) level but some symbols are still removed by the higher-level api builder) , i.e `\\` from the string's macro function `regex` contents as reported at [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd)
- sync the `golang.org/x/sys/unix` vendor
## The most important
We've made static files served up to 8 times faster using the new tool, <https://github.com/kataras/bindata> which is a fork of your beloved `go-bindata`, some unnecessary things for us were removed there and contains some additions for performance boost.
## Reqs/sec with [shuLhan/go-bindata](https://github.com/shuLhan/go-bindata) and alternatives
![go-bindata](https://github.com/kataras/bindata/raw/master/go-bindata-benchmark.png)
## Reqs/sec with [kataras/bindata](https://github.com/kataras/bindata)
![bindata](https://github.com/kataras/bindata/raw/master/bindata-benchmark.png)
A **new** function `Party#StaticEmbeddedGzip` which has the same input arguments as the `Party#StaticEmbedded` added. The difference is that the **new** `StaticEmbeddedGzip` accepts the `GzipAsset` and `GzipAssetNames` from the `bindata` (go get -u github.com/kataras/bindata/cmd/bindata).
You can still use both `bindata` and `go-bindata` tools in the same folder, the first for embedding the rest of the static files (javascript, css, ...) and the second for embedding the templates!
A full example can be found at: [_examples/file-server/embedding-gziped-files-into-app/main.go](_examples/file-server/embedding-gziped-files-into-app/main.go).
_Happy Coding!_
# Sa, 10 March 2018 | v10.3.0
- The only one API Change is the [Application/Context/Router#RouteExists](https://godoc.org/github.com/kataras/iris/core/router#Router.RouteExists), it accepts the `Context` as its first argument instead of last now.

View File

@ -17,9 +17,13 @@
**Πώς να αναβαθμίσετε**: Ανοίξτε την γραμμή εντολών σας και εκτελέστε αυτήν την εντολή: `go get -u github.com/kataras/iris` ή αφήστε το αυτόματο updater να το κάνει αυτό για σας.
# We, 14 March 2018 | v10.4.0
This history entry is not translated yet to the Greek language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#we-14-march-2018--v1040) instead.
# Sa, 10 March 2018 | v10.3.0
This history entry is not translated yet to the Greek language yet, please refer to the original [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#sa-10-march-2018--v1030) instead.
This history entry is not translated yet to the Greek language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#sa-10-march-2018--v1030) instead.
# Th, 15 February 2018 | v10.2.1

View File

@ -17,6 +17,10 @@
**如何升级**: 打开命令行执行以下命令: `go get -u github.com/kataras/iris` 或者等待自动更新。
# We, 14 March 2018 | v10.4.0
This history entry is not translated yet to the Chinese language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#we-14-march-2018--v1040) instead.
# 2018 3月10号 | v10.3.0 版本更新
- 只有一项 API 更改 [Application/Context/Router#RouteExists](https://godoc.org/github.com/kataras/iris/core/router#Router.RouteExists), 将 `Context` 作为第一参数,而不是最后一个。

View File

@ -2,7 +2,7 @@
<a href="https://iris-go.com"> <img align="right" width="169px" src="https://iris-go.com/images/icon.svg?v=a" title="logo created by @merry.dii" /> </a>
[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris)<!-- [![release](https://img.shields.io/github/release/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/releases)--> [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[![github closed issues](https://img.shields.io/github/issues-closed-raw/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.3-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases)
[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris)<!-- [![release](https://img.shields.io/github/release/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/releases)--> [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[![github closed issues](https://img.shields.io/github/issues-closed-raw/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.4-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases)
Iris is a fast, simple yet fully featured and very efficient web framework for Go.
@ -106,7 +106,7 @@ _Updated at: [Tuesday, 21 November 2017](_benchmarks/README_UNIX.md)_
## Support
- [HISTORY](HISTORY.md#sa-10-march-2018--v1030) file is your best friend, it contains information about the latest features and changes
- [HISTORY](HISTORY.md#we-14-march-2018--v1040) file is your best friend, it contains information about the latest features and changes
- Did you happen to find a bug? Post it at [github issues](https://github.com/kataras/iris/issues)
- Do you have any questions or need to speak with someone experienced to solve a problem at real-time? Join us to the [community chat](https://chat.iris-go.com)
- Complete our form-based user experience report by clicking [here](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link)

View File

@ -2,7 +2,7 @@
<a href="https://iris-go.com"> <img align="right" width="169px" src="https://iris-go.com/images/icon.svg?v=a" title="logo created by @merry.dii" /> </a>
[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris)<!-- [![release](https://img.shields.io/github/release/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/releases)--> [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[![github closed issues](https://img.shields.io/github/issues-closed-raw/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.3-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases)
[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris)<!-- [![release](https://img.shields.io/github/release/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/releases)--> [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[![github closed issues](https://img.shields.io/github/issues-closed-raw/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.4-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases)
Το Iris είναι ένα γρήγορο, απλό αλλά και πλήρως λειτουργικό και πολύ αποδοτικό web framework για τη Go.
@ -108,7 +108,7 @@ _Η τελευταία ενημέρωση έγινε την [Τρίτη, 21 Νο
## Υποστήριξη
- To [HISTORY](HISTORY_GR.md#sa-10-march-2018--v1030) αρχείο είναι ο καλύτερος σας φίλος, περιέχει πληροφορίες σχετικά με τις τελευταίες λειτουργίες(features) και αλλαγές
- To [HISTORY](HISTORY_GR.md#we-14-march-2018--v1040) αρχείο είναι ο καλύτερος σας φίλος, περιέχει πληροφορίες σχετικά με τις τελευταίες λειτουργίες(features) και αλλαγές
- Μήπως τυχαίνει να βρήκατε κάποιο bug; Δημοσιεύστε το στα [github issues](https://github.com/kataras/iris/issues)
- Έχετε οποιεσδήποτε ερωτήσεις ή πρέπει να μιλήσετε με κάποιον έμπειρο για την επίλυση ενός προβλήματος σε πραγματικό χρόνο; Ελάτε μαζί μας στην [συνομιλία κοινότητας](https://chat.iris-go.com)
- Συμπληρώστε την αναφορά εμπειρίας χρήστη κάνοντας κλικ [εδώ](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link)

View File

@ -2,7 +2,7 @@
<a href="https://iris-go.com"> <img align="right" width="169px" src="https://iris-go.com/images/icon.svg?v=a" title="logo created by @merry.dii" /> </a>
[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris)<!-- [![release](https://img.shields.io/github/release/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/releases)--> [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[![github closed issues](https://img.shields.io/github/issues-closed-raw/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.3-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases)
[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris)<!-- [![release](https://img.shields.io/github/release/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/releases)--> [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[![github closed issues](https://img.shields.io/github/issues-closed-raw/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.4-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases)
Iris - это быстрая, простая, но полнофункциональная и очень эффективная веб-платформа для Go.
@ -106,7 +106,7 @@ _Обновлено: [Вторник, 21 ноября 2017 г.](_benchmarks/READ
## Поддержка
- Файл [HISTORY](HISTORY.md#sa-10-march-2018--v1030) - ваш лучший друг, он содержит информацию о последних особенностях и всех изменениях
- Файл [HISTORY](HISTORY.md#we-14-march-2018--v1040) - ваш лучший друг, он содержит информацию о последних особенностях и всех изменениях
- Вы случайно обнаружили ошибку? Опубликуйте ее на [Github вопросы](https://github.com/kataras/iris/issues)
- У Вас есть какие-либо вопросы или Вам нужно поговорить с кем-то, кто бы смог решить Вашу проблему в режиме реального времени? Присоединяйтесь к нам в [чате сообщества](https://chat.iris-go.com)
- Заполните наш отчет о пользовательском опыте на основе формы, нажав [здесь](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link)

View File

@ -2,7 +2,7 @@
<a href="https://iris-go.com"> <img align="right" width="169px" src="https://iris-go.com/images/icon.svg?v=a" title="logo created by @merry.dii" /> </a>
[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris)<!-- [![release](https://img.shields.io/github/release/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/releases)--> [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[![github closed issues](https://img.shields.io/github/issues-closed-raw/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.3-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases)
[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris)<!-- [![release](https://img.shields.io/github/release/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/releases)--> [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[![github closed issues](https://img.shields.io/github/issues-closed-raw/kataras/iris.svg?style=flat-square)](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.4-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases)
Iris 是一款超快、简洁高效的 Go 语言 Web开发框架。
@ -102,7 +102,7 @@ _更新于: [2017年11月21日星期二](_benchmarks/README_UNIX.md)_
## 支持
- [更新记录](HISTORY_ZH.md#sa-10-march-2018--v1030) 是您最好的朋友,它包含有关最新功能和更改的信息
- [更新记录](HISTORY_ZH.md#we-14-march-2018--v1040) 是您最好的朋友,它包含有关最新功能和更改的信息
- 你碰巧找到了一个错误? 请提交 [github issues](https://github.com/kataras/iris/issues)
- 您是否有任何疑问或需要与有经验的人士交谈以实时解决问题? [加入我们的聊天](https://chat.iris-go.com)
- [点击这里完成我们基于表单的用户体验报告](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link)

View File

@ -1 +1 @@
10.3.0:https://github.com/kataras/iris/blob/master/HISTORY.md#sa-10-march-2018--v1030
10.4.0:https://github.com/kataras/iris/blob/master/HISTORY.md#we-14-march-2018--v1040

View File

@ -310,6 +310,7 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her
- [Favicon](file-server/favicon/main.go)
- [Basic](file-server/basic/main.go)
- [Embedding Files Into App Executable File](file-server/embedding-files-into-app/main.go)
- [Embedding Gziped Files Into App Executable File](file-server/embedding-gziped-files-into-app/main.go) **NEW**
- [Send/Force-Download Files](file-server/send-files/main.go)
- Single Page Applications
* [single Page Application](file-server/single-page-application/basic/main.go)

View File

@ -10,7 +10,8 @@ import (
// $ go build
// $ ./embedding-files-into-app
// "physical" files are not used, you can delete the "assets" folder and run the example.
//
// See `file-server/embedding-gziped-files-into-app` example as well.
func newApp() *iris.Application {
app := iris.New()

View File

@ -12,6 +12,24 @@ import (
type resource string
// content types that are used in the ./assets,
// we could use the detectContentType that iris do but it's better
// to do it manually so we can test if that returns the correct result on embedding files.
func (r resource) contentType() string {
switch filepath.Ext(r.String()) {
case ".js":
return "application/javascript"
case ".css":
return "text/css"
case ".ico":
return "image/x-icon"
case ".html":
return "text/html"
default:
return "text/plain"
}
}
func (r resource) String() string {
return string(r)
}
@ -34,6 +52,7 @@ func (r resource) loadFromBase(dir string) string {
}
result := string(b)
if runtime.GOOS != "windows" {
result = strings.Replace(result, "\n", "\r\n", -1)
}
@ -65,6 +84,7 @@ func TestEmbeddingFilesIntoApp(t *testing.T) {
e.GET(url).Expect().
Status(httptest.StatusOK).
ContentType(u.contentType(), app.ConfigurationReadOnly().GetCharset()).
Body().Equal(contents)
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
package main
import (
"github.com/kataras/iris"
)
// NOTE: need different tool than the "embedding-files-into-app" example.
//
// Follow these steps first:
// $ go get -u github.com/kataras/bindata/cmd/bindata
// $ bindata ./assets/...
// $ go build
// $ ./embedding-gziped-files-into-app
// "physical" files are not used, you can delete the "assets" folder and run the example.
func newApp() *iris.Application {
app := iris.New()
// Note the `GzipAsset` and `GzipAssetNames` are different from `go-bindata`'s `Asset` and `AssetNames,
// that means that you can use both `go-bindata` and `bindata` tools,
// the `go-bindata` can be used for the view engine's `Binary` method
// and the `bindata` with the `StaticEmbeddedGzip` (x8 times faster than the StaticEmbeded with `go-bindata`).
app.StaticEmbeddedGzip("/static", "./assets", GzipAsset, GzipAssetNames)
return app
}
func main() {
app := newApp()
// http://localhost:8080/static/css/bootstrap.min.css
// http://localhost:8080/static/js/jquery-2.1.1.js
// http://localhost:8080/static/favicon.ico
app.Run(iris.Addr(":8080"))
}

View File

@ -0,0 +1,106 @@
package main
import (
"bytes"
"io/ioutil"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/kataras/iris/httptest"
"github.com/klauspost/compress/gzip"
)
type resource string
// content types that are used in the ./assets,
// we could use the detectContentType that iris do but it's better
// to do it manually so we can test if that returns the correct result on embedding files.
func (r resource) contentType() string {
switch filepath.Ext(r.String()) {
case ".js":
return "application/javascript"
case ".css":
return "text/css"
case ".ico":
return "image/x-icon"
case ".html":
return "text/html"
default:
return "text/plain"
}
}
func (r resource) String() string {
return string(r)
}
func (r resource) strip(strip string) string {
s := r.String()
return strings.TrimPrefix(s, strip)
}
func (r resource) loadFromBase(dir string) string {
filename := r.String()
filename = r.strip("/static")
fullpath := filepath.Join(dir, filename)
b, err := ioutil.ReadFile(fullpath)
if err != nil {
panic(fullpath + " failed with error: " + err.Error())
}
result := string(b)
if runtime.GOOS != "windows" {
result = strings.Replace(result, "\n", "\r\n", -1)
}
return result
}
var urls = []resource{
"/static/css/bootstrap.min.css",
"/static/js/jquery-2.1.1.js",
"/static/favicon.ico",
}
// if bindata's values matches with the assets/... contents
// and secondly if the StaticEmbedded had successfully registered
// the routes and gave the correct response.
func TestEmbeddingGzipFilesIntoApp(t *testing.T) {
app := newApp()
e := httptest.New(t, app)
if runtime.GOOS != "windows" {
// remove the embedded static favicon for !windows,
// it should be built for unix-specific in order to be work
urls = urls[0 : len(urls)-1]
}
for i, u := range urls {
url := u.String()
rawContents := u.loadFromBase("./assets")
response := e.GET(url).Expect()
response.ContentType(u.contentType(), app.ConfigurationReadOnly().GetCharset())
if expected, got := response.Raw().StatusCode, httptest.StatusOK; expected != got {
t.Fatalf("[%d] of '%s': expected %d status code but got %d", i, url, expected, got)
}
func() {
reader, err := gzip.NewReader(bytes.NewBuffer(response.Content))
defer reader.Close()
if err != nil {
t.Fatalf("[%d] of '%s': %v", i, url, err)
}
buf := new(bytes.Buffer)
reader.WriteTo(buf)
if rawContents != buf.String() {
t.Fatalf("[%d] of '%s': expected body:\n%s but got:\n%s", i, url, rawContents, buf.String())
}
}()
}
}

View File

@ -22,7 +22,7 @@ func newApp() *iris.Application {
ctx.View("index.html")
})
assetHandler := app.StaticEmbeddedHandler("./public", Asset, AssetNames)
assetHandler := iris.StaticEmbeddedHandler("./public", Asset, AssetNames, false) // keep that false if you use the `go-bindata` tool.
// as an alternative of SPA you can take a look at the /routing/dynamic-path/root-wildcard
// example too
// or

View File

@ -12,6 +12,17 @@ import (
type resource string
func (r resource) contentType() string {
switch filepath.Ext(r.String()) {
case ".js":
return "application/javascript"
case ".css":
return "text/css"
default:
return "text/html"
}
}
func (r resource) String() string {
return string(r)
}
@ -59,6 +70,7 @@ func TestSPAEmbedded(t *testing.T) {
e.GET(url).Expect().
Status(httptest.StatusOK).
ContentType(u.contentType(), app.ConfigurationReadOnly().GetCharset()).
Body().Equal(contents)
}
}

139
cache/browser.go vendored Normal file
View File

@ -0,0 +1,139 @@
package cache
import (
"strconv"
"time"
"github.com/kataras/iris/cache/client"
"github.com/kataras/iris/context"
)
// CacheControlHeaderValue is the header value of the
// "Cache-Control": "private, no-cache, max-age=0, must-revalidate, no-store, proxy-revalidate, s-maxage=0".
//
// It can be overriden.
var CacheControlHeaderValue = "private, no-cache, max-age=0, must-revalidate, no-store, proxy-revalidate, s-maxage=0"
const (
// PragmaHeaderKey is the header key of "Pragma".
PragmaHeaderKey = "Pragma"
// PragmaNoCacheHeaderValue is the header value of "Pragma": "no-cache".
PragmaNoCacheHeaderValue = "no-cache"
// ExpiresHeaderKey is the header key of "Expires".
ExpiresHeaderKey = "Expires"
// ExpiresNeverHeaderValue is the header value of "ExpiresHeaderKey": "0".
ExpiresNeverHeaderValue = "0"
)
// NoCache is a middleware which overrides the Cache-Control, Pragma and Expires headers
// in order to disable the cache during the browser's back and forward feature.
//
// A good use of this middleware is on HTML routes; to refresh the page even on "back" and "forward" browser's arrow buttons.
//
// See `cache#StaticCache` for the opposite behavior.
var NoCache = func(ctx context.Context) {
ctx.Header(context.CacheControlHeaderKey, CacheControlHeaderValue)
ctx.Header(PragmaHeaderKey, PragmaNoCacheHeaderValue)
ctx.Header(ExpiresHeaderKey, ExpiresNeverHeaderValue)
// Add the X-No-Cache header as well, for any customized case, i.e `cache#Handler` or `cache#Cache`.
client.NoCache(ctx)
ctx.Next()
}
// StaticCache middleware for caching static files by sending the "Cache-Control" and "Expires" headers to the client.
// It accepts a single input parameter, the "cacheDur", a time.Duration that it's used to calculate the expiration.
//
// If "cacheDur" <=0 then it returns the `NoCache` middleware instaed to disable the caching between browser's "back" and "forward" actions.
//
// Usage: `app.Use(cache.StaticCache(24 * time.Hour))` or `app.Use(cache.Staticcache(-1))`.
// A middleware, which is a simple Handler can be called inside another handler as well, example:
// cacheMiddleware := cache.StaticCache(...)
// func(ctx iris.Context){
// cacheMiddleware(ctx)
// [...]
// }
var StaticCache = func(cacheDur time.Duration) context.Handler {
if int64(cacheDur) <= 0 {
return NoCache
}
cacheControlHeaderValue := "public, max-age=" + strconv.Itoa(int(cacheDur.Seconds()))
return func(ctx context.Context) {
cacheUntil := time.Now().Add(cacheDur).Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())
ctx.Header(ExpiresHeaderKey, cacheUntil)
ctx.Header(context.CacheControlHeaderKey, cacheControlHeaderValue)
ctx.Next()
}
}
const ifNoneMatchHeaderKey = "If-None-Match"
// ETag is another browser & server cache request-response feature.
// It can be used side by side with the `StaticCache`, usually `StaticCache` middleware should go first.
// This should be used on routes that serves static files only.
// The key of the `ETag` is the `ctx.Request().URL.Path`, invalidation of the not modified cache method
// can be made by other request handler as well.
//
// In typical usage, when a URL is retrieved, the web server will return the resource's current
// representation along with its corresponding ETag value,
// which is placed in an HTTP response header "ETag" field:
//
// ETag: "/mypath"
//
// The client may then decide to cache the representation, along with its ETag.
// Later, if the client wants to retrieve the same URL resource again,
// it will first determine whether the local cached version of the URL has expired
// (through the Cache-Control (`StaticCache` method) and the Expire headers).
// If the URL has not expired, it will retrieve the local cached resource.
// If it determined that the URL has expired (is stale), then the client will contact the server
// and send its previously saved copy of the ETag along with the request in a "If-None-Match" field.
//
// Usage with combination of `StaticCache`:
// assets := app.Party("/assets", cache.StaticCache(24 * time.Hour), ETag)
// assets.StaticWeb("/", "./assets") or StaticEmbedded("/", "./assets") or StaticEmbeddedGzip("/", "./assets").
//
// Similar to `Cache304` but it doesn't depends on any "modified date", it uses just the ETag and If-None-Match headers.
//
// Read more at: https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching and
// https://en.wikipedia.org/wiki/HTTP_ETag
var ETag = func(ctx context.Context) {
key := ctx.Request().URL.Path
ctx.Header(context.ETagHeaderKey, key)
if match := ctx.GetHeader(ifNoneMatchHeaderKey); match == key {
ctx.WriteNotModified()
return
}
ctx.Next()
}
// Cache304 sends a `StatusNotModified` (304) whenever
// the "If-Modified-Since" request header (time) is before the
// time.Now() + expiresEvery (always compared to their UTC values).
// Use this `cache#Cache304` instead of the "github.com/kataras/iris/cache" or iris.Cache
// for better performance.
// Clients that are compatible with the http RCF (all browsers are and tools like postman)
// will handle the caching.
// The only disadvantage of using that instead of server-side caching
// is that this method will send a 304 status code instead of 200,
// So, if you use it side by side with other micro services
// you have to check for that status code as well for a valid response.
//
// Developers are free to extend this method's behavior
// by watching system directories changes manually and use of the `ctx.WriteWithExpiration`
// with a "modtime" based on the file modified date,
// can be used on Party's that contains a static handler,
// i.e `StaticWeb`, `StaticEmbedded` or even `StaticEmbeddedGzip`.
var Cache304 = func(expiresEvery time.Duration) context.Handler {
return func(ctx context.Context) {
now := time.Now()
if modified, err := ctx.CheckIfModifiedSince(now.Add(-expiresEvery)); !modified && err == nil {
ctx.WriteNotModified()
return
}
ctx.SetLastModified(now)
ctx.Next()
}
}

103
cache/browser_test.go vendored Normal file
View File

@ -0,0 +1,103 @@
package cache_test
import (
"strconv"
"testing"
"time"
"github.com/kataras/iris/cache"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/httptest"
)
func TestNoCache(t *testing.T) {
t.Parallel()
app := iris.New()
app.Get("/", cache.NoCache, func(ctx iris.Context) {
ctx.WriteString("no_cache")
})
// tests
e := httptest.New(t, app)
r := e.GET("/").Expect().Status(httptest.StatusOK)
r.Body().Equal("no_cache")
r.Header(context.CacheControlHeaderKey).Equal(cache.CacheControlHeaderValue)
r.Header(cache.PragmaHeaderKey).Equal(cache.PragmaNoCacheHeaderValue)
r.Header(cache.ExpiresHeaderKey).Equal(cache.ExpiresNeverHeaderValue)
}
func TestStaticCache(t *testing.T) {
t.Parallel()
// test change the time format, which is not reccomended but can be done.
app := iris.New().Configure(iris.WithTimeFormat("02 Jan 2006 15:04:05 GMT"))
cacheDur := 30 * (24 * time.Hour)
var expectedTime time.Time
app.Get("/", cache.StaticCache(cacheDur), func(ctx iris.Context) {
expectedTime = time.Now()
ctx.WriteString("static_cache")
})
// tests
e := httptest.New(t, app)
r := e.GET("/").Expect().Status(httptest.StatusOK)
r.Body().Equal("static_cache")
r.Header(cache.ExpiresHeaderKey).Equal(expectedTime.Add(cacheDur).Format(app.ConfigurationReadOnly().GetTimeFormat()))
cacheControlHeaderValue := "public, max-age=" + strconv.Itoa(int(cacheDur.Seconds()))
r.Header(context.CacheControlHeaderKey).Equal(cacheControlHeaderValue)
}
func TestCache304(t *testing.T) {
t.Parallel()
app := iris.New()
expiresEvery := 4 * time.Second
app.Get("/", cache.Cache304(expiresEvery), func(ctx iris.Context) {
ctx.WriteString("send")
})
// handlers
e := httptest.New(t, app)
// when 304, content type, content length and if ETagg is there are removed from the headers.
insideCacheTimef := time.Now().Add(-expiresEvery).UTC().Format(app.ConfigurationReadOnly().GetTimeFormat())
r := e.GET("/").WithHeader(context.IfModifiedSinceHeaderKey, insideCacheTimef).Expect().Status(httptest.StatusNotModified)
r.Headers().NotContainsKey(context.ContentTypeHeaderKey).NotContainsKey(context.ContentLengthHeaderKey).NotContainsKey("ETag")
r.Body().Equal("")
// continue to the handler itself.
cacheInvalidatedTimef := time.Now().Add(expiresEvery).UTC().Format(app.ConfigurationReadOnly().GetTimeFormat()) // after ~5seconds.
r = e.GET("/").WithHeader(context.LastModifiedHeaderKey, cacheInvalidatedTimef).Expect().Status(httptest.StatusOK)
r.Body().Equal("send")
// now without header, it should continue to the handler itself as well.
r = e.GET("/").Expect().Status(httptest.StatusOK)
r.Body().Equal("send")
}
func TestETag(t *testing.T) {
t.Parallel()
app := iris.New()
n := "_"
app.Get("/", cache.ETag, func(ctx iris.Context) {
ctx.WriteString(n)
n += "_"
})
// the first and last test writes the content with status OK without cache,
// the rest tests the cache headers and status 304 and return, so body should be "".
e := httptest.New(t, app)
r := e.GET("/").Expect().Status(httptest.StatusOK)
r.Header("ETag").Equal("/") // test if header setted.
r.Body().Equal("_")
e.GET("/").WithHeader("ETag", "/").WithHeader("If-None-Match", "/").Expect().
Status(httptest.StatusNotModified).Body().Equal("") // browser is responsible, no the test engine.
r = e.GET("/").Expect().Status(httptest.StatusOK)
r.Header("ETag").Equal("/") // test if header setted.
r.Body().Equal("__")
}

6
cache/cache.go vendored
View File

@ -65,9 +65,3 @@ func Handler(expiration time.Duration) context.Handler {
h := Cache(expiration).ServeHTTP
return h
}
var (
// NoCache disables the cache for a particular request,
// can be used as a middleware or called manually from the handler.
NoCache = client.NoCache
)

5
cache/cache_test.go vendored
View File

@ -7,6 +7,7 @@ import (
"time"
"github.com/kataras/iris/cache"
"github.com/kataras/iris/cache/client"
"github.com/kataras/iris/cache/client/rule"
"github.com/kataras/iris"
@ -84,7 +85,7 @@ func runTest(e *httpexpect.Expect, path string, counterPtr *uint32, expectedBody
return nil
}
func TestNoCache(t *testing.T) {
func TestClientNoCache(t *testing.T) {
app := iris.New()
var n uint32
@ -94,7 +95,7 @@ func TestNoCache(t *testing.T) {
})
app.Get("/nocache", cache.Handler(cacheDuration), func(ctx context.Context) {
cache.NoCache(ctx) // <----
client.NoCache(ctx) // <----
atomic.AddUint32(&n, 1)
ctx.Write([]byte(expectedBodyStr))
})

View File

@ -980,35 +980,6 @@ var LimitRequestBodySize = func(maxRequestBodySizeBytes int64) Handler {
}
}
// Cache304 sends a `StatusNotModified` (304) whenever
// the "If-Modified-Since" request header (time) is before the
// time.Now() + expiresEvery (always compared to their UTC values).
// Use this `context#Cache304` instead of the "github.com/kataras/iris/cache" or iris.Cache
// for better performance.
// Clients that are compatible with the http RCF (all browsers are and tools like postman)
// will handle the caching.
// The only disadvantage of using that instead of server-side caching
// is that this method will send a 304 status code instead of 200,
// So, if you use it side by side with other micro services
// you have to check for that status code as well for a valid response.
//
// Developers are free to extend this method's behavior
// by watching system directories changes manually and use of the `ctx.WriteWithExpiration`
// with a "modtime" based on the file modified date,
// simillary to the `StaticWeb`(StaticWeb sends an OK(200) and browser disk caching instead of 304).
var Cache304 = func(expiresEvery time.Duration) Handler {
return func(ctx Context) {
now := time.Now()
if modified, err := ctx.CheckIfModifiedSince(now.Add(-expiresEvery)); !modified && err == nil {
ctx.WriteNotModified()
return
}
ctx.SetLastModified(now)
ctx.Next()
}
}
// Gzip is a middleware which enables writing
// using gzip compression, if client supports.
var Gzip = func(ctx Context) {
@ -1602,8 +1573,6 @@ func (ctx *context) Header(name string, value string) {
ctx.writer.Header().Add(name, value)
}
const contentTypeHeaderKey = "Content-Type"
// ContentType sets the response writer's header key "Content-Type" to the 'cType'.
func (ctx *context) ContentType(cType string) {
if cType == "" {
@ -1623,13 +1592,13 @@ func (ctx *context) ContentType(cType string) {
}
}
ctx.writer.Header().Set(contentTypeHeaderKey, cType)
ctx.writer.Header().Set(ContentTypeHeaderKey, cType)
}
// GetContentType returns the response writer's header value of "Content-Type"
// which may, setted before with the 'ContentType'.
func (ctx *context) GetContentType() string {
return ctx.writer.Header().Get(contentTypeHeaderKey)
return ctx.writer.Header().Get(ContentTypeHeaderKey)
}
// StatusCode sets the status code header to the response.
@ -2198,18 +2167,31 @@ func (ctx *context) WriteString(body string) (n int, err error) {
return ctx.writer.WriteString(body)
}
var (
// StaticCacheDuration expiration duration for INACTIVE file handlers, it's the only one global configuration
// which can be changed.
StaticCacheDuration = 20 * time.Second
const (
// ContentTypeHeaderKey is the header key of "Content-Type".
ContentTypeHeaderKey = "Content-Type"
lastModifiedHeaderKey = "Last-Modified"
ifModifiedSinceHeaderKey = "If-Modified-Since"
contentDispositionHeaderKey = "Content-Disposition"
cacheControlHeaderKey = "Cache-Control"
contentEncodingHeaderKey = "Content-Encoding"
acceptEncodingHeaderKey = "Accept-Encoding"
varyHeaderKey = "Vary"
// LastModifiedHeaderKey is the header key of "Last-Modified".
LastModifiedHeaderKey = "Last-Modified"
// IfModifiedSinceHeaderKey is the header key of "If-Modified-Since".
IfModifiedSinceHeaderKey = "If-Modified-Since"
// CacheControlHeaderKey is the header key of "Cache-Control".
CacheControlHeaderKey = "Cache-Control"
// ETagHeaderKey is the header key of "ETag".
ETagHeaderKey = "ETag"
// ContentDispositionHeaderKey is the header key of "Content-Disposition".
ContentDispositionHeaderKey = "Content-Disposition"
// ContentLengthHeaderKey is the header key of "Content-Length"
ContentLengthHeaderKey = "Content-Length"
// ContentEncodingHeaderKey is the header key of "Content-Encoding".
ContentEncodingHeaderKey = "Content-Encoding"
// GzipHeaderValue is the header value of "gzip".
GzipHeaderValue = "gzip"
// AcceptEncodingHeaderKey is the header key of "Accept-Encoding".
AcceptEncodingHeaderKey = "Accept-Encoding"
// VaryHeaderKey is the header key of "Vary".
VaryHeaderKey = "Vary"
)
var unixEpochTime = time.Unix(0, 0)
@ -2250,7 +2232,7 @@ var FormatTime = func(ctx Context, t time.Time) string {
// It's mostly internally on core/router and context packages.
func (ctx *context) SetLastModified(modtime time.Time) {
if !IsZeroTime(modtime) {
ctx.Header(lastModifiedHeaderKey, FormatTime(ctx, modtime.UTC())) // or modtime.UTC()?
ctx.Header(LastModifiedHeaderKey, FormatTime(ctx, modtime.UTC())) // or modtime.UTC()?
}
}
@ -2272,7 +2254,7 @@ func (ctx *context) CheckIfModifiedSince(modtime time.Time) (bool, error) {
if method := ctx.Method(); method != http.MethodGet && method != http.MethodHead {
return false, errors.New("skip: method")
}
ims := ctx.GetHeader(ifModifiedSinceHeaderKey)
ims := ctx.GetHeader(IfModifiedSinceHeaderKey)
if ims == "" || IsZeroTime(modtime) {
return false, errors.New("skip: zero time")
}
@ -2300,10 +2282,10 @@ func (ctx *context) WriteNotModified() {
// guiding cache updates (e.g.," Last-Modified" might be useful if the
// response does not have an ETag field).
h := ctx.ResponseWriter().Header()
delete(h, contentTypeHeaderKey)
delete(h, contentLengthHeaderKey)
if h.Get("Etag") != "" {
delete(h, lastModifiedHeaderKey)
delete(h, ContentTypeHeaderKey)
delete(h, ContentLengthHeaderKey)
if h.Get(ETagHeaderKey) != "" {
delete(h, LastModifiedHeaderKey)
}
ctx.StatusCode(http.StatusNotModified)
}
@ -2358,9 +2340,9 @@ func (ctx *context) StreamWriter(writer func(w io.Writer) bool) {
// ClientSupportsGzip retruns true if the client supports gzip compression.
func (ctx *context) ClientSupportsGzip() bool {
if h := ctx.GetHeader(acceptEncodingHeaderKey); h != "" {
if h := ctx.GetHeader(AcceptEncodingHeaderKey); h != "" {
for _, v := range strings.Split(h, ";") {
if strings.Contains(v, "gzip") { // we do Contains because sometimes browsers has the q=, we don't use it atm. || strings.Contains(v,"deflate"){
if strings.Contains(v, GzipHeaderValue) { // we do Contains because sometimes browsers has the q=, we don't use it atm. || strings.Contains(v,"deflate"){
return true
}
}
@ -2895,11 +2877,6 @@ var (
errServeContent = errors.New("while trying to serve content to the client. Trace %s")
)
const (
// contentLengthHeaderKey represents the header["Content-Length"]
contentLengthHeaderKey = "Content-Length"
)
// ServeContent serves content, headers are autoset
// receives three parameters, it's low-level function, instead you can use .ServeFile(string,bool)/SendFile(string,string)
//
@ -2915,8 +2892,7 @@ func (ctx *context) ServeContent(content io.ReadSeeker, filename string, modtime
ctx.SetLastModified(modtime)
var out io.Writer
if gzipCompression && ctx.ClientSupportsGzip() {
ctx.writer.Header().Add(varyHeaderKey, acceptEncodingHeaderKey)
ctx.Header(contentEncodingHeaderKey, "gzip")
AddGzipHeaders(ctx.writer)
gzipWriter := acquireGzipWriter(ctx.writer)
defer releaseGzipWriter(gzipWriter)
@ -2955,7 +2931,7 @@ func (ctx *context) ServeFile(filename string, gzipCompression bool) error {
//
// Use this instead of ServeFile to 'force-download' bigger files to the client.
func (ctx *context) SendFile(filename string, destinationName string) error {
ctx.writer.Header().Set(contentDispositionHeaderKey, "attachment;filename="+destinationName)
ctx.writer.Header().Set(ContentDispositionHeaderKey, "attachment;filename="+destinationName)
return ctx.ServeFile(filename, false)
}
@ -3029,7 +3005,7 @@ var maxAgeExp = regexp.MustCompile(`maxage=(\d+)`)
// seconds as int64
// if header not found or parse failed then it returns -1.
func (ctx *context) MaxAge() int64 {
header := ctx.GetHeader(cacheControlHeaderKey)
header := ctx.GetHeader(CacheControlHeaderKey)
if header == "" {
return -1
}
@ -3191,7 +3167,6 @@ func (ctx *context) Exec(method string, path string) {
req.RequestURI = path
req.URL.Path = path
req.Method = method
req.Host = req.Host
// execute the route from the (internal) context router
// this way we keep the sessions and the values

View File

@ -108,7 +108,7 @@ func (w *GzipResponseWriter) EndResponse() {
func (w *GzipResponseWriter) Write(contents []byte) (int, error) {
// save the contents to serve them (only gzip data here)
w.chunks = append(w.chunks, contents...)
return len(w.chunks), nil
return len(contents), nil
}
// Writef formats according to a format specifier and writes to the response.
@ -117,8 +117,8 @@ func (w *GzipResponseWriter) Write(contents []byte) (int, error) {
func (w *GzipResponseWriter) Writef(format string, a ...interface{}) (n int, err error) {
n, err = fmt.Fprintf(w, format, a...)
if err == nil {
if w.ResponseWriter.Header()[contentTypeHeaderKey] == nil {
w.ResponseWriter.Header().Set(contentTypeHeaderKey, ContentTextHeaderValue)
if w.ResponseWriter.Header()[ContentTypeHeaderKey] == nil {
w.ResponseWriter.Header().Set(ContentTypeHeaderKey, ContentTextHeaderValue)
}
}
@ -130,8 +130,8 @@ func (w *GzipResponseWriter) Writef(format string, a ...interface{}) (n int, err
func (w *GzipResponseWriter) WriteString(s string) (n int, err error) {
n, err = w.Write([]byte(s))
if err == nil {
if w.ResponseWriter.Header()[contentTypeHeaderKey] == nil {
w.ResponseWriter.Header().Set(contentTypeHeaderKey, ContentTextHeaderValue)
if w.ResponseWriter.Header()[ContentTypeHeaderKey] == nil {
w.ResponseWriter.Header().Set(ContentTypeHeaderKey, ContentTextHeaderValue)
}
}
@ -165,12 +165,11 @@ func (w *GzipResponseWriter) WriteNow(contents []byte) (int, error) {
// Or a better idea, acquire and adapt the gzip writer on-time when is not disabled.
// So that is not needed any more:
// w.gzipWriter.Reset(noop)
return w.ResponseWriter.Write(contents)
}
w.ResponseWriter.Header().Add(varyHeaderKey, "Accept-Encoding")
w.ResponseWriter.Header().Add(contentEncodingHeaderKey, "gzip")
AddGzipHeaders(w.ResponseWriter)
// if not `WriteNow` but "Content-Length" header
// is exists, then delete it before `.Write`
// Content-Length should not be there.
@ -178,6 +177,13 @@ func (w *GzipResponseWriter) WriteNow(contents []byte) (int, error) {
return writeGzip(w.ResponseWriter, contents)
}
// AddGzipHeaders just adds the headers "Vary" to "Accept-Encoding"
// and "Content-Encoding" to "gzip".
func AddGzipHeaders(w ResponseWriter) {
w.Header().Add(VaryHeaderKey, AcceptEncodingHeaderKey)
w.Header().Add(ContentEncodingHeaderKey, GzipHeaderValue)
}
// FlushResponse validates the response headers in order to be compatible with the gzip written data
// and writes the data to the underline ResponseWriter.
func (w *GzipResponseWriter) FlushResponse() {

View File

@ -85,7 +85,9 @@ func (w *ResponseRecorder) EndResponse() {
// possible to maximize compatibility.
func (w *ResponseRecorder) Write(contents []byte) (int, error) {
w.chunks = append(w.chunks, contents...)
return len(w.chunks), nil
// Remember that we should not return all the written length within `Write`:
// see https://github.com/kataras/iris/pull/931
return len(contents), nil
}
// Writef formats according to a format specifier and writes to the response.

View File

@ -114,7 +114,7 @@ func (t *Transaction) Complete(err error) {
reason = errWstatus.Reason
}
// get the content type used on this transaction
if cTypeH := t.context.ResponseWriter().Header().Get(contentTypeHeaderKey); cTypeH != "" {
if cTypeH := t.context.ResponseWriter().Header().Get(ContentTypeHeaderKey); cTypeH != "" {
cType = cTypeH
}

View File

@ -13,7 +13,7 @@ import (
const (
// Version is the string representation of the current local Iris Web Framework version.
Version = "10.3.0"
Version = "10.4.0"
)
// CheckForUpdates checks for any available updates

View File

@ -549,23 +549,6 @@ func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) (ro
return
}
// StaticCacheDuration expiration duration for INACTIVE file handlers, it's the only one global configuration
// which can be changed.
var StaticCacheDuration = 20 * time.Second
const (
lastModifiedHeaderKey = "Last-Modified"
ifModifiedSinceHeaderKey = "If-Modified-Since"
contentDispositionHeaderKey = "Content-Disposition"
cacheControlHeaderKey = "Cache-Control"
contentEncodingHeaderKey = "Content-Encoding"
acceptEncodingHeaderKey = "Accept-Encoding"
// contentLengthHeaderKey represents the header["Content-Length"]
contentLengthHeaderKey = "Content-Length"
contentTypeHeaderKey = "Content-Type"
varyHeaderKey = "Vary"
)
func (api *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) *Route {
api.Head(reqPath, h)
return api.Get(reqPath, h)
@ -627,28 +610,37 @@ func (api *APIBuilder) StaticContent(reqPath string, cType string, content []byt
return api.registerResourceRoute(reqPath, h)
}
// StaticEmbeddedHandler returns a Handler which can serve
// embedded into executable files.
//
//
// Examples: https://github.com/kataras/iris/tree/master/_examples/file-server
func (api *APIBuilder) StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) context.Handler {
// Notes:
// This doesn't need to be APIBuilder's scope,
// but we'll keep it here for consistently.
return StaticEmbeddedHandler(vdir, assetFn, namesFn)
}
// StaticEmbedded used when files are distributed 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"
// Second parameter is the (virtual) directory path, for example "./assets" (no trailing slash),
// Third parameter is the Asset function
// Forth parameter is the AssetNames function.
//
// Returns the GET *Route.
//
// Examples: https://github.com/kataras/iris/tree/master/_examples/file-server
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-files-into-app
func (api *APIBuilder) StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route {
return api.staticEmbedded(requestPath, vdir, assetFn, namesFn, false)
}
// StaticEmbeddedGzip registers a route which can serve embedded gziped files
// that are embedded using the https://github.com/kataras/bindata tool and only.
// It's 8 times faster than the `StaticEmbeddedHandler` with `go-bindata` but
// it sends gzip response only, so the client must be aware that is expecting a gzip body
// (browsers and most modern browsers do that, so you can use it without fair).
//
// 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" (no trailing slash),
// Third parameter is the GzipAsset function
// Forth parameter is the GzipAssetNames function.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-gziped-files-into-app
func (api *APIBuilder) StaticEmbeddedGzip(requestPath string, vdir string, gzipAssetFn func(name string) ([]byte, error), gzipNamesFn func() []string) *Route {
return api.staticEmbedded(requestPath, vdir, gzipAssetFn, gzipNamesFn, true)
}
// look fs.go#StaticEmbeddedHandler
func (api *APIBuilder) staticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string, assetsGziped bool) *Route {
fullpath := joinPath(api.relativePath, requestPath)
// if subdomain,
// here we get the full path of the path only,
@ -656,9 +648,10 @@ func (api *APIBuilder) StaticEmbedded(requestPath string, vdir string, assetFn f
// and we need that path to call the `StripPrefix`.
_, fullpath = splitSubdomainAndPath(fullpath)
requestPath = joinPath(fullpath, WildcardParam("file"))
paramName := "file"
requestPath = joinPath(requestPath, WildcardParam(paramName))
h := api.StaticEmbeddedHandler(vdir, assetFn, namesFn)
h := StaticEmbeddedHandler(vdir, assetFn, namesFn, assetsGziped)
if fullpath != "/" {
h = StripPrefix(fullpath, h)
@ -745,16 +738,17 @@ func (api *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
//
// Returns the GET *Route.
func (api *APIBuilder) StaticWeb(requestPath string, systemPath string) *Route {
paramName := "file"
fullpath := joinPath(api.relativePath, requestPath)
// if subdomain,
// here we get the full path of the path only,
// because a subdomain can have parties as well
// and we need that path to call the `StripPrefix`.
_, fullpath = splitSubdomainAndPath(fullpath)
requestPath = joinPath(fullpath, WildcardParam(paramName))
paramName := "file"
requestPath = joinPath(requestPath, WildcardParam(paramName))
h := NewStaticHandlerBuilder(systemPath).Listing(false).Build()
if fullpath != "/" {

View File

@ -21,12 +21,11 @@ import (
"github.com/kataras/iris/context"
)
// StaticEmbeddedHandler returns a Handler which can serve
// embedded into executable files.
//
// StaticEmbeddedHandler returns a Handler which can serve embedded files
// that are embedded using the go-bindata tool(assetsGziped = false) or the kataras/bindata tool (assetsGziped = true).
//
// Examples: https://github.com/kataras/iris/tree/master/_examples/file-server
func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) context.Handler {
func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string, assetsGziped bool) context.Handler {
// Depends on the command the user gave to the go-bindata
// the assset path (names) may be or may not be prepended with a slash.
// What we do: we remove the ./ from the vdir which should be
@ -43,6 +42,14 @@ func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error
if vdir[0] == '/' || vdir[0] == os.PathSeparator { // second check for /something, (or ./something if we had dot on 0 it will be removed
vdir = vdir[1:]
}
// check for trailing slashes because new users may be do that by mistake
// although all examples are showing the correct way but you never know
// i.e "./assets/" is not correct, if was inside "./assets".
// remove last "/".
if trailingSlashIdx := len(vdir) - 1; vdir[trailingSlashIdx] == '/' {
vdir = vdir[0:trailingSlashIdx]
}
}
// collect the names we are care for,
@ -61,8 +68,9 @@ func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error
names = append(names, path)
}
modtime := time.Now()
// modtime := time.Now()
h := func(ctx context.Context) {
reqPath := strings.TrimPrefix(ctx.Request().URL.Path, "/"+vdir)
// i.e : /css/main.css
@ -80,11 +88,19 @@ func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error
buf, err := assetFn(path) // remove the first slash
if assetsGziped {
// this will add the "Vary" : "Accept-Encoding"
// and "Content-Encoding": "gzip"
// headers.
context.AddGzipHeaders(ctx.ResponseWriter())
}
if err != nil {
continue
}
ctx.ContentType(cType)
if _, err := ctx.WriteWithExpiration(buf, modtime); err != nil {
if _, err := ctx.Write(buf); err != nil {
ctx.StatusCode(http.StatusInternalServerError)
ctx.StopExecution()
}
@ -93,7 +109,6 @@ func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error
// not found or error
ctx.NotFound()
}
return h
@ -140,6 +155,7 @@ type fsHandler struct {
// user options, only directory is required.
directory http.Dir
listDirectories bool
gzip bool
// these are init on the Build() call
filesystem http.FileSystem
once sync.Once
@ -183,18 +199,17 @@ func NewStaticHandlerBuilder(dir string) StaticHandlerBuilder {
}
}
// Gzip if enable is true then gzip compression is enabled for this static directory
// Defaults to false
// Gzip if enable is true then gzip compression is enabled for this static directory.
//
// Defaults to false.
func (w *fsHandler) Gzip(enable bool) StaticHandlerBuilder {
w.begin = append(w.begin, func(ctx context.Context) {
ctx.Gzip(true)
ctx.Next()
})
w.gzip = enable
return w
}
// Listing turn on/off the 'show files and directories'.
// Defaults to false
//
// Defaults to false.
func (w *fsHandler) Listing(listDirectoriesOnOff bool) StaticHandlerBuilder {
w.listDirectories = listDirectoriesOnOff
return w
@ -243,9 +258,15 @@ func (w *fsHandler) Build() context.Handler {
// Note the request.url.path is changed but request.RequestURI is not
// so on custom errors we use the requesturi instead.
// this can be changed
// this can be changed.
// take the gzip setting.
gzipEnabled := w.gzip
if !gzipEnabled {
// if false then check if the dev did something like `ctx.Gzip(true)`.
_, gzipEnabled = ctx.ResponseWriter().(*context.GzipResponseWriter)
}
_, gzipEnabled := ctx.ResponseWriter().(*context.GzipResponseWriter)
_, prevStatusCode := serveFile(ctx,
w.filesystem,
path.Clean(upath),
@ -272,15 +293,10 @@ func (w *fsHandler) Build() context.Handler {
return
}
// go to the next middleware
// go to the next middleware, if any.
ctx.Next()
}
if len(w.begin) > 0 {
handlers := append(w.begin[0:], fileserver)
w.handler = func(ctx context.Context) {
ctx.Do(handlers)
}
}
w.handler = fileserver
})
@ -294,10 +310,10 @@ func (w *fsHandler) Build() context.Handler {
// replying with an HTTP 404 not found error.
//
// Usage:
// fileserver := iris.StaticHandler("./static_files", false, false)
// fileserver := Party#StaticHandler("./static_files", false, false)
// h := router.StripPrefix("/static", fileserver)
// app.Get("/static", h)
//
// app.Get("/static/{f:path}", h)
// app.Head("/static/{f:path}", h)
func StripPrefix(prefix string, h context.Handler) context.Handler {
if prefix == "" {
return h
@ -501,8 +517,8 @@ func serveContent(ctx context.Context, name string, modtime time.Time, sizeFunc
}()
}
ctx.Header("Accept-Ranges", "bytes")
if ctx.ResponseWriter().Header().Get(contentEncodingHeaderKey) == "" {
ctx.Header(contentLengthHeaderKey, strconv.FormatInt(sendSize, 10))
if ctx.ResponseWriter().Header().Get(context.ContentEncodingHeaderKey) == "" {
ctx.Header(context.ContentLengthHeaderKey, strconv.FormatInt(sendSize, 10))
}
}

View File

@ -188,7 +188,15 @@ type Party interface {
//
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-files-into-app
StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route
// StaticEmbeddedGzip registers a route which can serve embedded gziped files
// that are embedded using the https://github.com/kataras/bindata tool and only.
// It's 8 times faster than the `StaticEmbeddedHandler` with `go-bindata` but
// it sends gzip response only, so the client must be aware that is expecting a gzip body
// (browsers and most modern browsers do that, so you can use it without fair).
//
//
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-gziped-files-into-app
StaticEmbeddedGzip(requestPath string, vdir string, gzipAssetFn func(name string) ([]byte, error), gzipNamesFn func() []string) *Route
// Favicon serves static favicon
// accepts 2 parameters, second is optional
// favPath (string), declare the system directory path of the __.ico

View File

@ -73,7 +73,7 @@ func joinPath(path1 string, path2 string) string {
// cleanPath applies the following rules
// iteratively until no further processing can be done:
//
// 1. Replace multiple slashes with a single slash.
// 1. Replace multiple slashes with a single slash.
// 2. Replace '\' with '/'
// 3. Replace "\\" with '/'
// 4. Ignore anything inside '{' and '}'
@ -87,7 +87,7 @@ func cleanPath(s string) string {
return "/"
}
// remove suffix "/".
// remove suffix "/", if it's root "/" then it will add it as a prefix below.
if lidx := len(s) - 1; s[lidx] == '/' {
s = s[:lidx]
}

View File

@ -9,6 +9,8 @@ func TestCleanPath(t *testing.T) {
path string
expected string
}{
{"/",
"/"},
{"noslashPrefix",
"/noslashPrefix"},
{"slashSuffix/",

2
doc.go
View File

@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub:
Current Version
10.3.0
10.4.0
Installation

46
iris.go
View File

@ -346,6 +346,18 @@ var (
//
// Examples: https://github.com/kataras/iris/tree/master/_examples/file-server
StaticEmbeddedHandler = router.StaticEmbeddedHandler
// 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.
//
// Usage:
// fileserver := Party#StaticHandler("./static_files", false, false)
// h := iris.StripPrefix("/static", fileserver)
// app.Get("/static/{f:path}", h)
// app.Head("/static/{f:path}", h)
StripPrefix = router.StripPrefix
// Gzip is a middleware which enables writing
// using gzip compression, if client supports.
//
@ -363,14 +375,38 @@ var (
// Cache is a middleware providing server-side cache functionalities
// to the next handlers, can be used as: `app.Get("/", iris.Cache, aboutHandler)`.
// It should be used after Static methods.
// See `context#Cache304` for an alternative, faster way.
// See `iris#Cache304` for an alternative, faster way.
//
// Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/#caching
Cache = cache.Handler
// NoCache is a middleware which overrides the Cache-Control, Pragma and Expires headers
// in order to disable the cache during the browser's back and forward feature.
//
// A good use of this middleware is on HTML routes; to refresh the page even on "back" and "forward" browser's arrow buttons.
//
// See `iris#StaticCache` for the opposite behavior.
//
// A shortcut of the `cache#NoCache`
NoCache = cache.NoCache
// StaticCache middleware for caching static files by sending the "Cache-Control" and "Expires" headers to the client.
// It accepts a single input parameter, the "cacheDur", a time.Duration that it's used to calculate the expiration.
//
// If "cacheDur" <=0 then it returns the `NoCache` middleware instaed to disable the caching between browser's "back" and "forward" actions.
//
// Usage: `app.Use(iris.StaticCache(24 * time.Hour))` or `app.Use(iris.Staticcache(-1))`.
// A middleware, which is a simple Handler can be called inside another handler as well, example:
// cacheMiddleware := iris.StaticCache(...)
// func(ctx iris.Context){
// cacheMiddleware(ctx)
// [...]
// }
//
// A shortcut of the `cache#StaticCache`
StaticCache = cache.StaticCache
// Cache304 sends a `StatusNotModified` (304) whenever
// the "If-Modified-Since" request header (time) is before the
// time.Now() + expiresEvery (always compared to their UTC values).
// Use this, which is a shortcut of the, `context#Cache304` instead of the "github.com/kataras/iris/cache" or iris.Cache
// Use this, which is a shortcut of the, `chache#Cache304` instead of the "github.com/kataras/iris/cache" or iris.Cache
// for better performance.
// Clients that are compatible with the http RCF (all browsers are and tools like postman)
// will handle the caching.
@ -382,10 +418,10 @@ var (
// Developers are free to extend this method's behavior
// by watching system directories changes manually and use of the `ctx.WriteWithExpiration`
// with a "modtime" based on the file modified date,
// simillary to the `StaticWeb`(StaticWeb sends an OK(200) and browser disk caching instead of 304).
// simillary to the `StaticWeb`(which sends status OK(200) and browser disk caching instead of 304).
//
// A shortcut of the `context#Cache304`.
Cache304 = context.Cache304
// A shortcut of the `cache#Cache304`.
Cache304 = cache.Cache304
)
// SPA accepts an "assetHandler" which can be the result of an