mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 02:06:28 +01:00
Merge pull request #11 from kataras/master
. Former-commit-id: 5c2fc434d0dfb1371753db2b229998a397761990
This commit is contained in:
commit
8973565acb
|
@ -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
2
Gopkg.lock
generated
|
@ -89,7 +89,7 @@
|
|||
branch = "master"
|
||||
name = "github.com/iris-contrib/httpexpect"
|
||||
packages = ["."]
|
||||
revision = "65e93247e7071782e760f82086e8b677cca0c78a"
|
||||
revision = "ebe99fcebbcedf6e7916320cce24c3e1832766ac"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
|
27
HISTORY.md
27
HISTORY.md
|
@ -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
|
||||
|
||||

|
||||
|
||||
## Reqs/sec with [kataras/bindata](https://github.com/kataras/bindata)
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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` 作为第一参数,而不是最后一个。
|
||||
|
|
|
@ -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>
|
||||
|
||||
[](https://travis-ci.org/kataras/iris)<!-- [](https://github.com/kataras/iris/releases)--> [](http://goreportcard.com/report/kataras/iris) [](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [](https://kataras.rocket.chat/channel/iris) [](https://iris-go.com/v10/recipe) [](https://github.com/kataras/iris/releases)
|
||||
[](https://travis-ci.org/kataras/iris)<!-- [](https://github.com/kataras/iris/releases)--> [](http://goreportcard.com/report/kataras/iris) [](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [](https://kataras.rocket.chat/channel/iris) [](https://iris-go.com/v10/recipe) [](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)
|
||||
|
|
|
@ -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>
|
||||
|
||||
[](https://travis-ci.org/kataras/iris)<!-- [](https://github.com/kataras/iris/releases)--> [](http://goreportcard.com/report/kataras/iris) [](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [](https://kataras.rocket.chat/channel/iris) [](https://iris-go.com/v10/recipe) [](https://github.com/kataras/iris/releases)
|
||||
[](https://travis-ci.org/kataras/iris)<!-- [](https://github.com/kataras/iris/releases)--> [](http://goreportcard.com/report/kataras/iris) [](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [](https://kataras.rocket.chat/channel/iris) [](https://iris-go.com/v10/recipe) [](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)
|
||||
|
|
|
@ -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>
|
||||
|
||||
[](https://travis-ci.org/kataras/iris)<!-- [](https://github.com/kataras/iris/releases)--> [](http://goreportcard.com/report/kataras/iris) [](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [](https://kataras.rocket.chat/channel/iris) [](https://iris-go.com/v10/recipe) [](https://github.com/kataras/iris/releases)
|
||||
[](https://travis-ci.org/kataras/iris)<!-- [](https://github.com/kataras/iris/releases)--> [](http://goreportcard.com/report/kataras/iris) [](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [](https://kataras.rocket.chat/channel/iris) [](https://iris-go.com/v10/recipe) [](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)
|
||||
|
|
|
@ -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>
|
||||
|
||||
[](https://travis-ci.org/kataras/iris)<!-- [](https://github.com/kataras/iris/releases)--> [](http://goreportcard.com/report/kataras/iris) [](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [](https://kataras.rocket.chat/channel/iris) [](https://iris-go.com/v10/recipe) [](https://github.com/kataras/iris/releases)
|
||||
[](https://travis-ci.org/kataras/iris)<!-- [](https://github.com/kataras/iris/releases)--> [](http://goreportcard.com/report/kataras/iris) [](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris)<!--[](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aclosed)--> [](https://kataras.rocket.chat/channel/iris) [](https://iris-go.com/v10/recipe) [](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)
|
||||
|
|
2
VERSION
2
VERSION
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
7225
_examples/file-server/embedding-gziped-files-into-app/assets/css/bootstrap.min.css
vendored
Normal file
7225
_examples/file-server/embedding-gziped-files-into-app/assets/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
9190
_examples/file-server/embedding-gziped-files-into-app/assets/js/jquery-2.1.1.js
vendored
Normal file
9190
_examples/file-server/embedding-gziped-files-into-app/assets/js/jquery-2.1.1.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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"))
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
139
cache/browser.go
vendored
Normal 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
103
cache/browser_test.go
vendored
Normal 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
6
cache/cache.go
vendored
|
@ -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
5
cache/cache_test.go
vendored
|
@ -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))
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 != "/" {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ func TestCleanPath(t *testing.T) {
|
|||
path string
|
||||
expected string
|
||||
}{
|
||||
{"/",
|
||||
"/"},
|
||||
{"noslashPrefix",
|
||||
"/noslashPrefix"},
|
||||
{"slashSuffix/",
|
||||
|
|
2
doc.go
2
doc.go
|
@ -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
46
iris.go
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user