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 # typescript examples
- cd ./typescript/_examples - cd ./typescript/_examples
- go get ./... - go get ./...
- go test -v -cover ./...
- cd ../../
# make sure that the _benchmarks code is working
- cd ./_benchmarks
- go get ./...
- go test -v -cover ./... - go test -v -cover ./...

2
Gopkg.lock generated
View File

@ -89,7 +89,7 @@
branch = "master" branch = "master"
name = "github.com/iris-contrib/httpexpect" name = "github.com/iris-contrib/httpexpect"
packages = ["."] packages = ["."]
revision = "65e93247e7071782e760f82086e8b677cca0c78a" revision = "ebe99fcebbcedf6e7916320cce24c3e1832766ac"
[[projects]] [[projects]]
branch = "master" 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. **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 # 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. - 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 να το κάνει αυτό για σας. **Πώς να αναβαθμίσετε**: Ανοίξτε την γραμμή εντολών σας και εκτελέστε αυτήν την εντολή: `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 # 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 # Th, 15 February 2018 | v10.2.1

View File

@ -17,6 +17,10 @@
**如何升级**: 打开命令行执行以下命令: `go get -u github.com/kataras/iris` 或者等待自动更新。 **如何升级**: 打开命令行执行以下命令: `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 版本更新 # 2018 3月10号 | v10.3.0 版本更新
- 只有一项 API 更改 [Application/Context/Router#RouteExists](https://godoc.org/github.com/kataras/iris/core/router#Router.RouteExists), 将 `Context` 作为第一参数,而不是最后一个。 - 只有一项 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> <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. 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 ## 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) - 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) - 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) - 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> <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. Το 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) - Μήπως τυχαίνει να βρήκατε κάποιο bug; Δημοσιεύστε το στα [github issues](https://github.com/kataras/iris/issues)
- Έχετε οποιεσδήποτε ερωτήσεις ή πρέπει να μιλήσετε με κάποιον έμπειρο για την επίλυση ενός προβλήματος σε πραγματικό χρόνο; Ελάτε μαζί μας στην [συνομιλία κοινότητας](https://chat.iris-go.com) - Έχετε οποιεσδήποτε ερωτήσεις ή πρέπει να μιλήσετε με κάποιον έμπειρο για την επίλυση ενός προβλήματος σε πραγματικό χρόνο; Ελάτε μαζί μας στην [συνομιλία κοινότητας](https://chat.iris-go.com)
- Συμπληρώστε την αναφορά εμπειρίας χρήστη κάνοντας κλικ [εδώ](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) - Συμπληρώστε την αναφορά εμπειρίας χρήστη κάνοντας κλικ [εδώ](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> <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. 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) - Вы случайно обнаружили ошибку? Опубликуйте ее на [Github вопросы](https://github.com/kataras/iris/issues)
- У Вас есть какие-либо вопросы или Вам нужно поговорить с кем-то, кто бы смог решить Вашу проблему в режиме реального времени? Присоединяйтесь к нам в [чате сообщества](https://chat.iris-go.com) - У Вас есть какие-либо вопросы или Вам нужно поговорить с кем-то, кто бы смог решить Вашу проблему в режиме реального времени? Присоединяйтесь к нам в [чате сообщества](https://chat.iris-go.com)
- Заполните наш отчет о пользовательском опыте на основе формы, нажав [здесь](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) - Заполните наш отчет о пользовательском опыте на основе формы, нажав [здесь](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> <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开发框架。 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) - 你碰巧找到了一个错误? 请提交 [github issues](https://github.com/kataras/iris/issues)
- 您是否有任何疑问或需要与有经验的人士交谈以实时解决问题? [加入我们的聊天](https://chat.iris-go.com) - 您是否有任何疑问或需要与有经验的人士交谈以实时解决问题? [加入我们的聊天](https://chat.iris-go.com)
- [点击这里完成我们基于表单的用户体验报告](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) - [点击这里完成我们基于表单的用户体验报告](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) - [Favicon](file-server/favicon/main.go)
- [Basic](file-server/basic/main.go) - [Basic](file-server/basic/main.go)
- [Embedding Files Into App Executable File](file-server/embedding-files-into-app/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) - [Send/Force-Download Files](file-server/send-files/main.go)
- Single Page Applications - Single Page Applications
* [single Page Application](file-server/single-page-application/basic/main.go) * [single Page Application](file-server/single-page-application/basic/main.go)

View File

@ -10,7 +10,8 @@ import (
// $ go build // $ go build
// $ ./embedding-files-into-app // $ ./embedding-files-into-app
// "physical" files are not used, you can delete the "assets" folder and run the example. // "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 { func newApp() *iris.Application {
app := iris.New() app := iris.New()

View File

@ -12,6 +12,24 @@ import (
type resource string 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 { func (r resource) String() string {
return string(r) return string(r)
} }
@ -34,6 +52,7 @@ func (r resource) loadFromBase(dir string) string {
} }
result := string(b) result := string(b)
if runtime.GOOS != "windows" { if runtime.GOOS != "windows" {
result = strings.Replace(result, "\n", "\r\n", -1) result = strings.Replace(result, "\n", "\r\n", -1)
} }
@ -65,6 +84,7 @@ func TestEmbeddingFilesIntoApp(t *testing.T) {
e.GET(url).Expect(). e.GET(url).Expect().
Status(httptest.StatusOK). Status(httptest.StatusOK).
ContentType(u.contentType(), app.ConfigurationReadOnly().GetCharset()).
Body().Equal(contents) 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") 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 // as an alternative of SPA you can take a look at the /routing/dynamic-path/root-wildcard
// example too // example too
// or // or

View File

@ -12,6 +12,17 @@ import (
type resource string 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 { func (r resource) String() string {
return string(r) return string(r)
} }
@ -59,6 +70,7 @@ func TestSPAEmbedded(t *testing.T) {
e.GET(url).Expect(). e.GET(url).Expect().
Status(httptest.StatusOK). Status(httptest.StatusOK).
ContentType(u.contentType(), app.ConfigurationReadOnly().GetCharset()).
Body().Equal(contents) 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 h := Cache(expiration).ServeHTTP
return h 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" "time"
"github.com/kataras/iris/cache" "github.com/kataras/iris/cache"
"github.com/kataras/iris/cache/client"
"github.com/kataras/iris/cache/client/rule" "github.com/kataras/iris/cache/client/rule"
"github.com/kataras/iris" "github.com/kataras/iris"
@ -84,7 +85,7 @@ func runTest(e *httpexpect.Expect, path string, counterPtr *uint32, expectedBody
return nil return nil
} }
func TestNoCache(t *testing.T) { func TestClientNoCache(t *testing.T) {
app := iris.New() app := iris.New()
var n uint32 var n uint32
@ -94,7 +95,7 @@ func TestNoCache(t *testing.T) {
}) })
app.Get("/nocache", cache.Handler(cacheDuration), func(ctx context.Context) { app.Get("/nocache", cache.Handler(cacheDuration), func(ctx context.Context) {
cache.NoCache(ctx) // <---- client.NoCache(ctx) // <----
atomic.AddUint32(&n, 1) atomic.AddUint32(&n, 1)
ctx.Write([]byte(expectedBodyStr)) 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 // Gzip is a middleware which enables writing
// using gzip compression, if client supports. // using gzip compression, if client supports.
var Gzip = func(ctx Context) { var Gzip = func(ctx Context) {
@ -1602,8 +1573,6 @@ func (ctx *context) Header(name string, value string) {
ctx.writer.Header().Add(name, value) ctx.writer.Header().Add(name, value)
} }
const contentTypeHeaderKey = "Content-Type"
// ContentType sets the response writer's header key "Content-Type" to the 'cType'. // ContentType sets the response writer's header key "Content-Type" to the 'cType'.
func (ctx *context) ContentType(cType string) { func (ctx *context) ContentType(cType string) {
if cType == "" { 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" // GetContentType returns the response writer's header value of "Content-Type"
// which may, setted before with the 'ContentType'. // which may, setted before with the 'ContentType'.
func (ctx *context) GetContentType() string { 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. // 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) return ctx.writer.WriteString(body)
} }
var ( const (
// StaticCacheDuration expiration duration for INACTIVE file handlers, it's the only one global configuration // ContentTypeHeaderKey is the header key of "Content-Type".
// which can be changed. ContentTypeHeaderKey = "Content-Type"
StaticCacheDuration = 20 * time.Second
lastModifiedHeaderKey = "Last-Modified" // LastModifiedHeaderKey is the header key of "Last-Modified".
ifModifiedSinceHeaderKey = "If-Modified-Since" LastModifiedHeaderKey = "Last-Modified"
contentDispositionHeaderKey = "Content-Disposition" // IfModifiedSinceHeaderKey is the header key of "If-Modified-Since".
cacheControlHeaderKey = "Cache-Control" IfModifiedSinceHeaderKey = "If-Modified-Since"
contentEncodingHeaderKey = "Content-Encoding" // CacheControlHeaderKey is the header key of "Cache-Control".
acceptEncodingHeaderKey = "Accept-Encoding" CacheControlHeaderKey = "Cache-Control"
varyHeaderKey = "Vary" // 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) 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. // It's mostly internally on core/router and context packages.
func (ctx *context) SetLastModified(modtime time.Time) { func (ctx *context) SetLastModified(modtime time.Time) {
if !IsZeroTime(modtime) { 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 { if method := ctx.Method(); method != http.MethodGet && method != http.MethodHead {
return false, errors.New("skip: method") return false, errors.New("skip: method")
} }
ims := ctx.GetHeader(ifModifiedSinceHeaderKey) ims := ctx.GetHeader(IfModifiedSinceHeaderKey)
if ims == "" || IsZeroTime(modtime) { if ims == "" || IsZeroTime(modtime) {
return false, errors.New("skip: zero time") 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 // guiding cache updates (e.g.," Last-Modified" might be useful if the
// response does not have an ETag field). // response does not have an ETag field).
h := ctx.ResponseWriter().Header() h := ctx.ResponseWriter().Header()
delete(h, contentTypeHeaderKey) delete(h, ContentTypeHeaderKey)
delete(h, contentLengthHeaderKey) delete(h, ContentLengthHeaderKey)
if h.Get("Etag") != "" { if h.Get(ETagHeaderKey) != "" {
delete(h, lastModifiedHeaderKey) delete(h, LastModifiedHeaderKey)
} }
ctx.StatusCode(http.StatusNotModified) 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. // ClientSupportsGzip retruns true if the client supports gzip compression.
func (ctx *context) ClientSupportsGzip() bool { func (ctx *context) ClientSupportsGzip() bool {
if h := ctx.GetHeader(acceptEncodingHeaderKey); h != "" { if h := ctx.GetHeader(AcceptEncodingHeaderKey); h != "" {
for _, v := range strings.Split(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 return true
} }
} }
@ -2895,11 +2877,6 @@ var (
errServeContent = errors.New("while trying to serve content to the client. Trace %s") 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 // ServeContent serves content, headers are autoset
// receives three parameters, it's low-level function, instead you can use .ServeFile(string,bool)/SendFile(string,string) // 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) ctx.SetLastModified(modtime)
var out io.Writer var out io.Writer
if gzipCompression && ctx.ClientSupportsGzip() { if gzipCompression && ctx.ClientSupportsGzip() {
ctx.writer.Header().Add(varyHeaderKey, acceptEncodingHeaderKey) AddGzipHeaders(ctx.writer)
ctx.Header(contentEncodingHeaderKey, "gzip")
gzipWriter := acquireGzipWriter(ctx.writer) gzipWriter := acquireGzipWriter(ctx.writer)
defer releaseGzipWriter(gzipWriter) 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. // Use this instead of ServeFile to 'force-download' bigger files to the client.
func (ctx *context) SendFile(filename string, destinationName string) error { 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) return ctx.ServeFile(filename, false)
} }
@ -3029,7 +3005,7 @@ var maxAgeExp = regexp.MustCompile(`maxage=(\d+)`)
// seconds as int64 // seconds as int64
// if header not found or parse failed then it returns -1. // if header not found or parse failed then it returns -1.
func (ctx *context) MaxAge() int64 { func (ctx *context) MaxAge() int64 {
header := ctx.GetHeader(cacheControlHeaderKey) header := ctx.GetHeader(CacheControlHeaderKey)
if header == "" { if header == "" {
return -1 return -1
} }
@ -3191,7 +3167,6 @@ func (ctx *context) Exec(method string, path string) {
req.RequestURI = path req.RequestURI = path
req.URL.Path = path req.URL.Path = path
req.Method = method req.Method = method
req.Host = req.Host
// execute the route from the (internal) context router // execute the route from the (internal) context router
// this way we keep the sessions and the values // 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) { func (w *GzipResponseWriter) Write(contents []byte) (int, error) {
// save the contents to serve them (only gzip data here) // save the contents to serve them (only gzip data here)
w.chunks = append(w.chunks, contents...) 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. // 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) { func (w *GzipResponseWriter) Writef(format string, a ...interface{}) (n int, err error) {
n, err = fmt.Fprintf(w, format, a...) n, err = fmt.Fprintf(w, format, a...)
if err == nil { if err == nil {
if w.ResponseWriter.Header()[contentTypeHeaderKey] == nil { if w.ResponseWriter.Header()[ContentTypeHeaderKey] == nil {
w.ResponseWriter.Header().Set(contentTypeHeaderKey, ContentTextHeaderValue) 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) { func (w *GzipResponseWriter) WriteString(s string) (n int, err error) {
n, err = w.Write([]byte(s)) n, err = w.Write([]byte(s))
if err == nil { if err == nil {
if w.ResponseWriter.Header()[contentTypeHeaderKey] == nil { if w.ResponseWriter.Header()[ContentTypeHeaderKey] == nil {
w.ResponseWriter.Header().Set(contentTypeHeaderKey, ContentTextHeaderValue) 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. // Or a better idea, acquire and adapt the gzip writer on-time when is not disabled.
// So that is not needed any more: // So that is not needed any more:
// w.gzipWriter.Reset(noop) // w.gzipWriter.Reset(noop)
return w.ResponseWriter.Write(contents) return w.ResponseWriter.Write(contents)
} }
w.ResponseWriter.Header().Add(varyHeaderKey, "Accept-Encoding") AddGzipHeaders(w.ResponseWriter)
w.ResponseWriter.Header().Add(contentEncodingHeaderKey, "gzip")
// if not `WriteNow` but "Content-Length" header // if not `WriteNow` but "Content-Length" header
// is exists, then delete it before `.Write` // is exists, then delete it before `.Write`
// Content-Length should not be there. // Content-Length should not be there.
@ -178,6 +177,13 @@ func (w *GzipResponseWriter) WriteNow(contents []byte) (int, error) {
return writeGzip(w.ResponseWriter, contents) 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 // FlushResponse validates the response headers in order to be compatible with the gzip written data
// and writes the data to the underline ResponseWriter. // and writes the data to the underline ResponseWriter.
func (w *GzipResponseWriter) FlushResponse() { func (w *GzipResponseWriter) FlushResponse() {

View File

@ -85,7 +85,9 @@ func (w *ResponseRecorder) EndResponse() {
// possible to maximize compatibility. // possible to maximize compatibility.
func (w *ResponseRecorder) Write(contents []byte) (int, error) { func (w *ResponseRecorder) Write(contents []byte) (int, error) {
w.chunks = append(w.chunks, contents...) 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. // 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 reason = errWstatus.Reason
} }
// get the content type used on this transaction // 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 cType = cTypeH
} }

View File

@ -13,7 +13,7 @@ import (
const ( const (
// Version is the string representation of the current local Iris Web Framework version. // 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 // CheckForUpdates checks for any available updates

View File

@ -549,23 +549,6 @@ func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) (ro
return 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 { func (api *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) *Route {
api.Head(reqPath, h) api.Head(reqPath, h)
return api.Get(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) 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 // 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" // 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 // Third parameter is the Asset function
// Forth parameter is the AssetNames function. // Forth parameter is the AssetNames function.
// //
// Returns the GET *Route. // 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 { 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) fullpath := joinPath(api.relativePath, requestPath)
// if subdomain, // if subdomain,
// here we get the full path of the path only, // 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`. // and we need that path to call the `StripPrefix`.
_, fullpath = splitSubdomainAndPath(fullpath) _, 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 != "/" { if fullpath != "/" {
h = StripPrefix(fullpath, h) h = StripPrefix(fullpath, h)
@ -745,16 +738,17 @@ func (api *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
// //
// Returns the GET *Route. // Returns the GET *Route.
func (api *APIBuilder) StaticWeb(requestPath string, systemPath string) *Route { func (api *APIBuilder) StaticWeb(requestPath string, systemPath string) *Route {
paramName := "file"
fullpath := joinPath(api.relativePath, requestPath) fullpath := joinPath(api.relativePath, requestPath)
// if subdomain, // if subdomain,
// here we get the full path of the path only, // here we get the full path of the path only,
// because a subdomain can have parties as well // because a subdomain can have parties as well
// and we need that path to call the `StripPrefix`. // and we need that path to call the `StripPrefix`.
_, fullpath = splitSubdomainAndPath(fullpath) _, fullpath = splitSubdomainAndPath(fullpath)
requestPath = joinPath(fullpath, WildcardParam(paramName)) paramName := "file"
requestPath = joinPath(requestPath, WildcardParam(paramName))
h := NewStaticHandlerBuilder(systemPath).Listing(false).Build() h := NewStaticHandlerBuilder(systemPath).Listing(false).Build()
if fullpath != "/" { if fullpath != "/" {

View File

@ -21,12 +21,11 @@ import (
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
) )
// StaticEmbeddedHandler returns a Handler which can serve // StaticEmbeddedHandler returns a Handler which can serve embedded files
// embedded into executable 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 // 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 // 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. // 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 // 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 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:] 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, // 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) names = append(names, path)
} }
modtime := time.Now() // modtime := time.Now()
h := func(ctx context.Context) { h := func(ctx context.Context) {
reqPath := strings.TrimPrefix(ctx.Request().URL.Path, "/"+vdir) reqPath := strings.TrimPrefix(ctx.Request().URL.Path, "/"+vdir)
// i.e : /css/main.css // 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 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 { if err != nil {
continue continue
} }
ctx.ContentType(cType) ctx.ContentType(cType)
if _, err := ctx.WriteWithExpiration(buf, modtime); err != nil { if _, err := ctx.Write(buf); err != nil {
ctx.StatusCode(http.StatusInternalServerError) ctx.StatusCode(http.StatusInternalServerError)
ctx.StopExecution() ctx.StopExecution()
} }
@ -93,7 +109,6 @@ func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error
// not found or error // not found or error
ctx.NotFound() ctx.NotFound()
} }
return h return h
@ -140,6 +155,7 @@ type fsHandler struct {
// user options, only directory is required. // user options, only directory is required.
directory http.Dir directory http.Dir
listDirectories bool listDirectories bool
gzip bool
// these are init on the Build() call // these are init on the Build() call
filesystem http.FileSystem filesystem http.FileSystem
once sync.Once 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 // Gzip if enable is true then gzip compression is enabled for this static directory.
// Defaults to false //
// Defaults to false.
func (w *fsHandler) Gzip(enable bool) StaticHandlerBuilder { func (w *fsHandler) Gzip(enable bool) StaticHandlerBuilder {
w.begin = append(w.begin, func(ctx context.Context) { w.gzip = enable
ctx.Gzip(true)
ctx.Next()
})
return w return w
} }
// Listing turn on/off the 'show files and directories'. // Listing turn on/off the 'show files and directories'.
// Defaults to false //
// Defaults to false.
func (w *fsHandler) Listing(listDirectoriesOnOff bool) StaticHandlerBuilder { func (w *fsHandler) Listing(listDirectoriesOnOff bool) StaticHandlerBuilder {
w.listDirectories = listDirectoriesOnOff w.listDirectories = listDirectoriesOnOff
return w return w
@ -243,9 +258,15 @@ func (w *fsHandler) Build() context.Handler {
// Note the request.url.path is changed but request.RequestURI is not // Note the request.url.path is changed but request.RequestURI is not
// so on custom errors we use the requesturi instead. // 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, _, prevStatusCode := serveFile(ctx,
w.filesystem, w.filesystem,
path.Clean(upath), path.Clean(upath),
@ -272,15 +293,10 @@ func (w *fsHandler) Build() context.Handler {
return return
} }
// go to the next middleware // go to the next middleware, if any.
ctx.Next() 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 w.handler = fileserver
}) })
@ -294,10 +310,10 @@ func (w *fsHandler) Build() context.Handler {
// replying with an HTTP 404 not found error. // replying with an HTTP 404 not found error.
// //
// Usage: // Usage:
// fileserver := iris.StaticHandler("./static_files", false, false) // fileserver := Party#StaticHandler("./static_files", false, false)
// h := router.StripPrefix("/static", fileserver) // 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 { func StripPrefix(prefix string, h context.Handler) context.Handler {
if prefix == "" { if prefix == "" {
return h return h
@ -501,8 +517,8 @@ func serveContent(ctx context.Context, name string, modtime time.Time, sizeFunc
}() }()
} }
ctx.Header("Accept-Ranges", "bytes") ctx.Header("Accept-Ranges", "bytes")
if ctx.ResponseWriter().Header().Get(contentEncodingHeaderKey) == "" { if ctx.ResponseWriter().Header().Get(context.ContentEncodingHeaderKey) == "" {
ctx.Header(contentLengthHeaderKey, strconv.FormatInt(sendSize, 10)) 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 // 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 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 // Favicon serves static favicon
// accepts 2 parameters, second is optional // accepts 2 parameters, second is optional
// favPath (string), declare the system directory path of the __.ico // 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 // cleanPath applies the following rules
// iteratively until no further processing can be done: // 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 '/' // 2. Replace '\' with '/'
// 3. Replace "\\" with '/' // 3. Replace "\\" with '/'
// 4. Ignore anything inside '{' and '}' // 4. Ignore anything inside '{' and '}'
@ -87,7 +87,7 @@ func cleanPath(s string) string {
return "/" 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] == '/' { if lidx := len(s) - 1; s[lidx] == '/' {
s = s[:lidx] s = s[:lidx]
} }

View File

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

2
doc.go
View File

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

46
iris.go
View File

@ -346,6 +346,18 @@ var (
// //
// Examples: https://github.com/kataras/iris/tree/master/_examples/file-server // Examples: https://github.com/kataras/iris/tree/master/_examples/file-server
StaticEmbeddedHandler = router.StaticEmbeddedHandler 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 // Gzip is a middleware which enables writing
// using gzip compression, if client supports. // using gzip compression, if client supports.
// //
@ -363,14 +375,38 @@ var (
// Cache is a middleware providing server-side cache functionalities // Cache is a middleware providing server-side cache functionalities
// to the next handlers, can be used as: `app.Get("/", iris.Cache, aboutHandler)`. // to the next handlers, can be used as: `app.Get("/", iris.Cache, aboutHandler)`.
// It should be used after Static methods. // 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 // Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/#caching
Cache = cache.Handler 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 // Cache304 sends a `StatusNotModified` (304) whenever
// the "If-Modified-Since" request header (time) is before the // the "If-Modified-Since" request header (time) is before the
// time.Now() + expiresEvery (always compared to their UTC values). // 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. // for better performance.
// Clients that are compatible with the http RCF (all browsers are and tools like postman) // Clients that are compatible with the http RCF (all browsers are and tools like postman)
// will handle the caching. // will handle the caching.
@ -382,10 +418,10 @@ var (
// Developers are free to extend this method's behavior // Developers are free to extend this method's behavior
// by watching system directories changes manually and use of the `ctx.WriteWithExpiration` // by watching system directories changes manually and use of the `ctx.WriteWithExpiration`
// with a "modtime" based on the file modified date, // 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`. // A shortcut of the `cache#Cache304`.
Cache304 = context.Cache304 Cache304 = cache.Cache304
) )
// SPA accepts an "assetHandler" which can be the result of an // SPA accepts an "assetHandler" which can be the result of an