mirror of
https://github.com/kataras/iris.git
synced 2025-02-09 02:34:55 +01:00
add vscode extension link and badge | Some internal improvements (not completed yet)
Former-commit-id: 9bc94e90a2780ee81f8188509d98063fb3f2924b
This commit is contained in:
parent
243439af9d
commit
bf13f7648a
10
FAQ.md
10
FAQ.md
|
@ -10,6 +10,14 @@ Add a `badge` to your open-source projects powered by [Iris](https://iris-go.com
|
||||||
|
|
||||||
> The badge is optionally, of course, it is just a simple and fast way to support Iris. The badge is work of a third-party, taken from https://github.com/blob-go/blob-go which was published by our friend @clover113 and we loved it<3
|
> The badge is optionally, of course, it is just a simple and fast way to support Iris. The badge is work of a third-party, taken from https://github.com/blob-go/blob-go which was published by our friend @clover113 and we loved it<3
|
||||||
|
|
||||||
|
## Editors & IDEs Extensions
|
||||||
|
|
||||||
|
### Visual Studio Code <a href="https://marketplace.visualstudio.com/items?itemName=kataras2006.iris"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/2d/Visual_Studio_Code_1.18_icon.svg/2000px-Visual_Studio_Code_1.18_icon.svg.png" height="20px" width="20px" /></a>
|
||||||
|
|
||||||
|
<https://marketplace.visualstudio.com/items?itemName=kataras2006.iris>
|
||||||
|
|
||||||
|
> Please feel free to list your own Iris extension(s) here by [PR](https://github.com/kataras/iris/pulls)
|
||||||
|
|
||||||
## How to upgrade
|
## How to upgrade
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
@ -18,7 +26,7 @@ go get -u github.com/kataras/iris
|
||||||
|
|
||||||
## Learning
|
## Learning
|
||||||
|
|
||||||
More than 50 practical examples, tutorials and articles at:
|
More than 100 practical examples, tutorials and articles at:
|
||||||
|
|
||||||
- https://github.com/kataras/iris/tree/master/_examples
|
- https://github.com/kataras/iris/tree/master/_examples
|
||||||
- https://github.com/iris-contrib/examples
|
- https://github.com/iris-contrib/examples
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<img align="right" width="169px" src="https://iris-go.com/images/icon.svg?v=a" title="logo created by @merry.dii" />
|
<img align="right" width="169px" src="https://iris-go.com/images/icon.svg?v=a" title="logo created by @merry.dii" />
|
||||||
|
|
||||||
[![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)<!--[![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)](_examples/) [![release](https://img.shields.io/badge/release%20-v10.0-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://github.com/kataras/vscode-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)](_examples/) [![release](https://img.shields.io/badge/release%20-v10.0-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.
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<img align="right" width="169px" src="https://iris-go.com/images/icon.svg?v=a" title="logo created by @merry.dii" />
|
<img align="right" width="169px" src="https://iris-go.com/images/icon.svg?v=a" title="logo created by @merry.dii" />
|
||||||
|
|
||||||
[![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)<!--[![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)](_examples/) [![release](https://img.shields.io/badge/release%20-v10.0-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://github.com/kataras/vscode-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)](_examples/) [![release](https://img.shields.io/badge/release%20-v10.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases)
|
||||||
|
|
||||||
Το Iris είναι ένα γρήγορο, απλό αλλά και πλήρως λειτουργικό και πολύ αποδοτικό web framework για τη Go.
|
Το Iris είναι ένα γρήγορο, απλό αλλά και πλήρως λειτουργικό και πολύ αποδοτικό web framework για τη Go.
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<img align="right" width="169px" src="https://iris-go.com/images/icon.svg?v=a" title="logo created by @merry.dii" />
|
<img align="right" width="169px" src="https://iris-go.com/images/icon.svg?v=a" title="logo created by @merry.dii" />
|
||||||
|
|
||||||
[![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)<!--[![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)](_examples/) [![release](https://img.shields.io/badge/release%20-v10.0-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://github.com/kataras/vscode-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)](_examples/) [![release](https://img.shields.io/badge/release%20-v10.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases)
|
||||||
|
|
||||||
Iris - это быстрая, простая, но полнофункциональная и очень эффективная веб-платформа для Go.
|
Iris - это быстрая, простая, но полнофункциональная и очень эффективная веб-платформа для Go.
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<img align="right" width="169px" src="https://iris-go.com/images/icon.svg?v=a" title="logo created by @merry.dii" />
|
<img align="right" width="169px" src="https://iris-go.com/images/icon.svg?v=a" title="logo created by @merry.dii" />
|
||||||
|
|
||||||
[![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)<!--[![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)](_examples/) [![release](https://img.shields.io/badge/release%20-v10.0-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://github.com/kataras/vscode-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)](_examples/) [![release](https://img.shields.io/badge/release%20-v10.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases)
|
||||||
|
|
||||||
Iris 是一款超快、简洁高效的 Go 语言 Web开发框架。
|
Iris 是一款超快、简洁高效的 Go 语言 Web开发框架。
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
func TestCasbinWrapper(t *testing.T) {
|
func TestCasbinWrapper(t *testing.T) {
|
||||||
app := newApp()
|
app := newApp()
|
||||||
e := httptest.New(t, app, httptest.Debug(true))
|
e := httptest.New(t, app)
|
||||||
|
|
||||||
type ttcasbin struct {
|
type ttcasbin struct {
|
||||||
username string
|
username string
|
||||||
|
@ -43,7 +43,6 @@ func TestCasbinWrapper(t *testing.T) {
|
||||||
check(e, tt.method, tt.path, tt.username, tt.status)
|
check(e, tt.method, tt.path, tt.username, tt.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
println("ADMIN ROLES")
|
|
||||||
ttAdmin := []ttcasbin{
|
ttAdmin := []ttcasbin{
|
||||||
{"cathrin", "/dataset1/item", "GET", 200},
|
{"cathrin", "/dataset1/item", "GET", 200},
|
||||||
{"cathrin", "/dataset1/item", "POST", 200},
|
{"cathrin", "/dataset1/item", "POST", 200},
|
||||||
|
@ -57,7 +56,6 @@ func TestCasbinWrapper(t *testing.T) {
|
||||||
check(e, tt.method, tt.path, tt.username, tt.status)
|
check(e, tt.method, tt.path, tt.username, tt.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
println("ADMIN ROLE FOR cathrin DELETED")
|
|
||||||
Enforcer.DeleteRolesForUser("cathrin")
|
Enforcer.DeleteRolesForUser("cathrin")
|
||||||
|
|
||||||
ttAdminDeleted := []ttcasbin{
|
ttAdminDeleted := []ttcasbin{
|
||||||
|
|
|
@ -42,7 +42,7 @@ func getSignupForm(ctx iris.Context) {
|
||||||
// views/signup.html just needs a {{ .csrfField }} template tag for
|
// views/signup.html just needs a {{ .csrfField }} template tag for
|
||||||
// csrf.TemplateField to inject the CSRF token into. Easy!
|
// csrf.TemplateField to inject the CSRF token into. Easy!
|
||||||
ctx.ViewData(csrf.TemplateTag, csrf.TemplateField(ctx))
|
ctx.ViewData(csrf.TemplateTag, csrf.TemplateField(ctx))
|
||||||
ctx.View("views/user/signup.html")
|
ctx.View("user/signup.html")
|
||||||
|
|
||||||
// We could also retrieve the token directly from csrf.Token(r) and
|
// We could also retrieve the token directly from csrf.Token(r) and
|
||||||
// set it in the request header - ctx.GetHeader("X-CSRF-Token", token)
|
// set it in the request header - ctx.GetHeader("X-CSRF-Token", token)
|
||||||
|
|
|
@ -24,10 +24,7 @@ func TestSubdomainRedirectWWW(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
req := e.GET(test.path)
|
e.GET(test.path).Expect().Status(httptest.StatusOK).Body().Equal(test.response)
|
||||||
// req.WithURL("http://www." + root)
|
|
||||||
|
|
||||||
req.Expect().Status(httptest.StatusOK).Body().Equal(test.response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,19 +30,19 @@ func newApp() *iris.Application {
|
||||||
|
|
||||||
www := app.Party("www.")
|
www := app.Party("www.")
|
||||||
{
|
{
|
||||||
|
// Just to show how you can get all routes and copy them to another
|
||||||
|
// party or subdomain:
|
||||||
|
// Get all routes that are registered so far, including all "Parties" and subdomains:
|
||||||
|
currentRoutes := app.GetRoutes()
|
||||||
|
// Register them to the www subdomain/vhost as well:
|
||||||
|
for _, r := range currentRoutes {
|
||||||
|
www.Handle(r.Method, r.Tmpl().Src, r.Handlers...)
|
||||||
|
}
|
||||||
|
|
||||||
// http://www.mydomain.com/hi
|
// http://www.mydomain.com/hi
|
||||||
www.Get("/hi", func(ctx iris.Context) {
|
www.Get("/hi", func(ctx iris.Context) {
|
||||||
ctx.Writef("hi from www.mydomain.com")
|
ctx.Writef("hi from www.mydomain.com")
|
||||||
})
|
})
|
||||||
|
|
||||||
// Just to show how you can get all routes and copy them to another
|
|
||||||
// party or subdomain:
|
|
||||||
// Get all routes that are registered so far, including all "Parties" but subdomains:
|
|
||||||
currentRoutes := app.GetRoutes()
|
|
||||||
// Register them to the www subdomain/vhost as well:
|
|
||||||
for _, r := range currentRoutes {
|
|
||||||
www.Handle(r.Method, r.Path, r.Handlers...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// See also the "subdomains/redirect" to register redirect router wrappers between subdomains,
|
// See also the "subdomains/redirect" to register redirect router wrappers between subdomains,
|
||||||
// i.e mydomain.com to www.mydomain.com (like facebook does for SEO reasons(;)).
|
// i.e mydomain.com to www.mydomain.com (like facebook does for SEO reasons(;)).
|
||||||
|
|
|
@ -41,13 +41,13 @@ func TestSubdomainWWW(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
host := "localhost:1111"
|
host := "localhost:1111"
|
||||||
e := httptest.New(t, app, httptest.URL("http://"+host))
|
e := httptest.New(t, app, httptest.URL("http://"+host), httptest.Debug(false))
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|
||||||
req := e.Request(test.method, test.path)
|
req := e.Request(test.method, test.path)
|
||||||
if subdomain := test.subdomain; subdomain != "" {
|
if subdomain := test.subdomain; subdomain != "" {
|
||||||
req.WithURL("http://" + subdomain + "." + host)
|
req = req.WithURL("http://" + subdomain + "." + host)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Expect().
|
req.Expect().
|
||||||
|
|
|
@ -891,18 +891,6 @@ type Context interface {
|
||||||
|
|
||||||
var _ Context = (*context)(nil)
|
var _ Context = (*context)(nil)
|
||||||
|
|
||||||
// Next calls all the next handler from the handlers chain,
|
|
||||||
// it should be used inside a middleware.
|
|
||||||
func Next(ctx Context) {
|
|
||||||
if ctx.IsStopped() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if n, handlers := ctx.HandlerIndex(-1)+1, ctx.Handlers(); n < len(handlers) {
|
|
||||||
ctx.HandlerIndex(n)
|
|
||||||
handlers[n](ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do calls the SetHandlers(handlers)
|
// Do calls the SetHandlers(handlers)
|
||||||
// and executes the first handler,
|
// and executes the first handler,
|
||||||
// handlers should not be empty.
|
// handlers should not be empty.
|
||||||
|
@ -1159,17 +1147,38 @@ func (ctx *context) HandlerName() string {
|
||||||
return HandlerName(ctx.handlers[ctx.currentHandlerIndex])
|
return HandlerName(ctx.handlers[ctx.currentHandlerIndex])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do sets the handler index to zero, executes the first handler
|
// Next is the function that executed when `ctx.Next()` is called.
|
||||||
// and the rest of the Handlers if ctx.Next() was called.
|
// It can be changed to a customized one if needed (very advanced usage).
|
||||||
// func (ctx *context) Do() {
|
//
|
||||||
// ctx.currentHandlerIndex = 0
|
// See `DefaultNext` for more information about this and why it's exported like this.
|
||||||
// ctx.handlers[0](ctx) // it calls this *context
|
var Next = DefaultNext ///TODO: add an example for this usecase, i.e describe handlers and skip only file handlers.
|
||||||
// } // -> replaced with inline on router.go
|
|
||||||
|
// DefaultNext is the default function that executed on each middleware if `ctx.Next()`
|
||||||
|
// is called.
|
||||||
|
//
|
||||||
|
// DefaultNext calls the next handler from the handlers chain by registration order,
|
||||||
|
// it should be used inside a middleware.
|
||||||
|
//
|
||||||
|
// It can be changed to a customized one if needed (very advanced usage).
|
||||||
|
//
|
||||||
|
// Developers are free to customize the whole or part of the Context's implementation
|
||||||
|
// by implementing a new `context.Context` (see https://github.com/kataras/iris/tree/master/_examples/routing/custom-context)
|
||||||
|
// or by just override the `context.Next` package-level field, `context.DefaultNext` is exported
|
||||||
|
// in order to be able for developers to merge your customized version one with the default behavior as well.
|
||||||
|
func DefaultNext(ctx Context) {
|
||||||
|
if ctx.IsStopped() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n, handlers := ctx.HandlerIndex(-1)+1, ctx.Handlers(); n < len(handlers) {
|
||||||
|
ctx.HandlerIndex(n)
|
||||||
|
handlers[n](ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Next calls all the next handler from the handlers chain,
|
// Next calls all the next handler from the handlers chain,
|
||||||
// it should be used inside a middleware.
|
// it should be used inside a middleware.
|
||||||
//
|
//
|
||||||
// Note: Custom context should override this method in order to be able to pass its own context.context implementation.
|
// Note: Custom context should override this method in order to be able to pass its own context.Context implementation.
|
||||||
func (ctx *context) Next() { // or context.Next(ctx)
|
func (ctx *context) Next() { // or context.Next(ctx)
|
||||||
Next(ctx)
|
Next(ctx)
|
||||||
}
|
}
|
||||||
|
@ -2046,29 +2055,111 @@ var (
|
||||||
varyHeaderKey = "Vary"
|
varyHeaderKey = "Vary"
|
||||||
)
|
)
|
||||||
|
|
||||||
// staticCachePassed checks the IfModifiedSince header and
|
var unixEpochTime = time.Unix(0, 0)
|
||||||
// returns true if (client-side) duration has expired
|
|
||||||
func (ctx *context) staticCachePassed(modtime time.Time) bool {
|
// IsZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
|
||||||
if t, err := time.Parse(ctx.Application().ConfigurationReadOnly().GetTimeFormat(), ctx.GetHeader(ifModifiedSinceHeaderKey)); err == nil && modtime.Before(t.Add(StaticCacheDuration)) {
|
func IsZeroTime(t time.Time) bool {
|
||||||
ctx.writer.Header().Del(contentTypeHeaderKey)
|
return t.IsZero() || t.Equal(unixEpochTime)
|
||||||
ctx.writer.Header().Del(contentLengthHeaderKey)
|
|
||||||
ctx.StatusCode(http.StatusNotModified)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
// ParseTime parses a time header (such as the Date: header),
|
||||||
|
// trying each forth formats (or three if Application's configuration's TimeFormat is defaulted)
|
||||||
|
// that are allowed by HTTP/1.1:
|
||||||
|
// Application's configuration's TimeFormat or/and http.TimeFormat,
|
||||||
|
// time.RFC850, and time.ANSIC.
|
||||||
|
//
|
||||||
|
// Look `context#FormatTime` for the opossite operation (Time to string).
|
||||||
|
var ParseTime = func(ctx Context, text string) (t time.Time, err error) {
|
||||||
|
t, err = time.Parse(ctx.Application().ConfigurationReadOnly().GetTimeFormat(), text)
|
||||||
|
if err != nil {
|
||||||
|
return http.ParseTime(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatTime returns a textual representation of the time value formatted
|
||||||
|
// according to the Application's configuration's TimeFormat field
|
||||||
|
// which defines the format.
|
||||||
|
//
|
||||||
|
// Look `context#ParseTime` for the opossite operation (string to Time).
|
||||||
|
var FormatTime = func(ctx Context, t time.Time) string {
|
||||||
|
return t.Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLastModified sets the "Last-Modified" based on the "modtime" input.
|
||||||
|
// If "modtime" is zero then it does nothing.
|
||||||
|
//
|
||||||
|
// It's mostly internally on core/router and context packages.
|
||||||
|
func SetLastModified(ctx Context, modtime time.Time) {
|
||||||
|
if !IsZeroTime(modtime) {
|
||||||
|
ctx.Header(lastModifiedHeaderKey, FormatTime(ctx, modtime)) // or modtime.UTC()?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckIfModifiedSince checks if the response is modified since the "modtime".
|
||||||
|
// Note that it has nothing to do with server-side caching.
|
||||||
|
// It does those checks by checking if the "If-Modified-Since" request header
|
||||||
|
// sent by client or a previous server response header
|
||||||
|
// (e.g with WriteWithExpiration or StaticEmbedded or Favicon etc.)
|
||||||
|
// is a valid one and it's before the "modtime".
|
||||||
|
//
|
||||||
|
// A check for !modtime && err == nil is necessary to make sure that
|
||||||
|
// it's not modified since, because it may return false but without even
|
||||||
|
// had the chance to check the client-side (request) header due to some errors,
|
||||||
|
// like the HTTP Method is not "GET" or "HEAD" or if the "modtime" is zero
|
||||||
|
// or if parsing time from the header failed.
|
||||||
|
//
|
||||||
|
// It's mostly used internally, e.g. `context#WriteWithExpiration`.
|
||||||
|
func CheckIfModifiedSince(ctx Context, 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)
|
||||||
|
if ims == "" || IsZeroTime(modtime) {
|
||||||
|
return false, errors.New("skip: zero time")
|
||||||
|
}
|
||||||
|
t, err := ParseTime(ctx, ims)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.New("skip: " + err.Error())
|
||||||
|
}
|
||||||
|
// sub-second precision, so
|
||||||
|
// use mtime < t+1s instead of mtime <= t to check for unmodified.
|
||||||
|
if modtime.Before(t.Add(1 * time.Second)) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteNotModified sends a 304 "Not Modified" status code to the client,
|
||||||
|
// it makes sure that the content type, the content length headers
|
||||||
|
// and any "ETag" are removed before the response sent.
|
||||||
|
//
|
||||||
|
// It's mostly used internally on core/router/fs.go and context methods.
|
||||||
|
func WriteNotModified(ctx Context) {
|
||||||
|
// RFC 7232 section 4.1:
|
||||||
|
// a sender SHOULD NOT generate representation metadata other than the
|
||||||
|
// above listed fields unless said metadata exists for the purpose of
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
ctx.StatusCode(http.StatusNotModified)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteWithExpiration like Write but it sends with an expiration datetime
|
// WriteWithExpiration like Write but it sends with an expiration datetime
|
||||||
// which is refreshed every package-level `StaticCacheDuration` field.
|
// which is refreshed every package-level `StaticCacheDuration` field.
|
||||||
func (ctx *context) WriteWithExpiration(body []byte, modtime time.Time) (int, error) {
|
func (ctx *context) WriteWithExpiration(body []byte, modtime time.Time) (int, error) {
|
||||||
|
if modified, err := CheckIfModifiedSince(ctx, modtime); !modified && err == nil {
|
||||||
if ctx.staticCachePassed(modtime) {
|
WriteNotModified(ctx)
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
modtimeFormatted := modtime.UTC().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())
|
SetLastModified(ctx, modtime)
|
||||||
ctx.Header(lastModifiedHeaderKey, modtimeFormatted)
|
|
||||||
|
|
||||||
return ctx.writer.Write(body)
|
return ctx.writer.Write(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2658,16 +2749,13 @@ const (
|
||||||
// You can define your own "Content-Type" header also, after this function call
|
// You can define your own "Content-Type" header also, after this function call
|
||||||
// Doesn't implements resuming (by range), use ctx.SendFile instead
|
// Doesn't implements resuming (by range), use ctx.SendFile instead
|
||||||
func (ctx *context) ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error {
|
func (ctx *context) ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error {
|
||||||
if t, err := time.Parse(ctx.Application().ConfigurationReadOnly().GetTimeFormat(), ctx.GetHeader(ifModifiedSinceHeaderKey)); err == nil && modtime.Before(t.Add(1*time.Second)) {
|
if modified, err := CheckIfModifiedSince(ctx, modtime); !modified && err == nil {
|
||||||
ctx.writer.Header().Del(contentTypeHeaderKey)
|
WriteNotModified(ctx)
|
||||||
ctx.writer.Header().Del(contentLengthHeaderKey)
|
|
||||||
ctx.StatusCode(http.StatusNotModified)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.ContentType(filename)
|
ctx.ContentType(filename)
|
||||||
ctx.writer.Header().Set(lastModifiedHeaderKey, modtime.UTC().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat()))
|
SetLastModified(ctx, modtime)
|
||||||
ctx.StatusCode(http.StatusOK)
|
|
||||||
var out io.Writer
|
var out io.Writer
|
||||||
if gzipCompression && ctx.ClientSupportsGzip() {
|
if gzipCompression && ctx.ClientSupportsGzip() {
|
||||||
ctx.writer.Header().Add(varyHeaderKey, acceptEncodingHeaderKey)
|
ctx.writer.Header().Add(varyHeaderKey, acceptEncodingHeaderKey)
|
||||||
|
@ -2680,7 +2768,7 @@ func (ctx *context) ServeContent(content io.ReadSeeker, filename string, modtime
|
||||||
out = ctx.writer
|
out = ctx.writer
|
||||||
}
|
}
|
||||||
_, err := io.Copy(out, content)
|
_, err := io.Copy(out, content)
|
||||||
return errServeContent.With(err)
|
return errServeContent.With(err) ///TODO: add an int64 as return value for the content length written like other writers or let it as it's in order to keep the stable api?
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeFile serves a view file, to send a file ( zip for example) to the client you should use the SendFile(serverfilename,clientfilename)
|
// ServeFile serves a view file, to send a file ( zip for example) to the client you should use the SendFile(serverfilename,clientfilename)
|
||||||
|
|
|
@ -629,7 +629,6 @@ func (api *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
|
||||||
return api.Favicon(path.Join(favPath, "favicon.ico"))
|
return api.Favicon(path.Join(favPath, "favicon.ico"))
|
||||||
}
|
}
|
||||||
|
|
||||||
cType := TypeByFilename(favPath)
|
|
||||||
// copy the bytes here in order to cache and not read the ico on each request.
|
// copy the bytes here in order to cache and not read the ico on each request.
|
||||||
cacheFav := make([]byte, fi.Size())
|
cacheFav := make([]byte, fi.Size())
|
||||||
if _, err = f.Read(cacheFav); err != nil {
|
if _, err = f.Read(cacheFav); err != nil {
|
||||||
|
@ -641,25 +640,14 @@ func (api *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
|
||||||
Format(favPath, "favicon: couldn't read the data bytes for file: "+err.Error()))
|
Format(favPath, "favicon: couldn't read the data bytes for file: "+err.Error()))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
modtime := ""
|
|
||||||
|
modtime := time.Now()
|
||||||
|
cType := TypeByFilename(favPath)
|
||||||
h := func(ctx context.Context) {
|
h := func(ctx context.Context) {
|
||||||
if modtime == "" {
|
ctx.ContentType(cType)
|
||||||
modtime = fi.ModTime().UTC().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())
|
if _, err := ctx.WriteWithExpiration(cacheFav, modtime); err != nil {
|
||||||
}
|
|
||||||
if t, err := time.Parse(ctx.Application().ConfigurationReadOnly().GetTimeFormat(), ctx.GetHeader(ifModifiedSinceHeaderKey)); err == nil && fi.ModTime().Before(t.Add(StaticCacheDuration)) {
|
|
||||||
|
|
||||||
ctx.ResponseWriter().Header().Del(contentTypeHeaderKey)
|
|
||||||
ctx.ResponseWriter().Header().Del(contentLengthHeaderKey)
|
|
||||||
ctx.StatusCode(http.StatusNotModified)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.ResponseWriter().Header().Set(contentTypeHeaderKey, cType)
|
|
||||||
ctx.ResponseWriter().Header().Set(lastModifiedHeaderKey, modtime)
|
|
||||||
ctx.StatusCode(http.StatusOK)
|
|
||||||
if _, err := ctx.Write(cacheFav); err != nil {
|
|
||||||
// ctx.Application().Logger().Infof("error while trying to serve the favicon: %s", err.Error())
|
|
||||||
ctx.StatusCode(http.StatusInternalServerError)
|
ctx.StatusCode(http.StatusInternalServerError)
|
||||||
|
ctx.Application().Logger().Debugf("while trying to serve the favicon: %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -698,16 +686,16 @@ func (api *APIBuilder) StaticWeb(requestPath string, systemPath string) *Route {
|
||||||
|
|
||||||
handler := func(ctx context.Context) {
|
handler := func(ctx context.Context) {
|
||||||
h(ctx)
|
h(ctx)
|
||||||
if ctx.GetStatusCode() >= 200 && ctx.GetStatusCode() < 400 {
|
// if ctx.GetStatusCode() >= 200 && ctx.GetStatusCode() < 400 {
|
||||||
// re-check the content type here for any case,
|
// // re-check the content type here for any case,
|
||||||
// although the new code does it automatically but it's good to have it here.
|
// // although the new code does it automatically but it's good to have it here.
|
||||||
if _, exists := ctx.ResponseWriter().Header()["Content-Type"]; !exists {
|
// if _, exists := ctx.ResponseWriter().Header()["Content-Type"]; !exists {
|
||||||
if fname := ctx.Params().Get(paramName); fname != "" {
|
// if fname := ctx.Params().Get(paramName); fname != "" {
|
||||||
cType := TypeByFilename(fname)
|
// cType := TypeByFilename(fname)
|
||||||
ctx.ContentType(cType)
|
// ctx.ContentType(cType)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
requestPath = joinPath(requestPath, WildcardParam(paramName))
|
requestPath = joinPath(requestPath, WildcardParam(paramName))
|
||||||
|
@ -791,16 +779,20 @@ func (api *APIBuilder) FireErrorCode(ctx context.Context) {
|
||||||
api.errorCodeHandlers.Fire(ctx)
|
api.errorCodeHandlers.Fire(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layout oerrides the parent template layout with a more specific layout for this Party
|
// Layout overrides the parent template layout with a more specific layout for this Party.
|
||||||
// returns this Party, to continue as normal
|
// It returns the current Party.
|
||||||
|
//
|
||||||
|
// The "tmplLayoutFile" should be a relative path to the templates dir.
|
||||||
// Usage:
|
// Usage:
|
||||||
|
//
|
||||||
// app := iris.New()
|
// app := iris.New()
|
||||||
|
// app.RegisterView(iris.$VIEW_ENGINE("./views", ".$extension"))
|
||||||
// my := app.Party("/my").Layout("layouts/mylayout.html")
|
// my := app.Party("/my").Layout("layouts/mylayout.html")
|
||||||
// {
|
// my.Get("/", func(ctx iris.Context) {
|
||||||
// my.Get("/", func(ctx context.Context) {
|
// ctx.View("page1.html")
|
||||||
// ctx.MustRender("page1.html", nil)
|
|
||||||
// })
|
// })
|
||||||
// }
|
//
|
||||||
|
// Examples: https://github.com/kataras/iris/tree/master/_examples/view
|
||||||
func (api *APIBuilder) Layout(tmplLayoutFile string) Party {
|
func (api *APIBuilder) Layout(tmplLayoutFile string) Party {
|
||||||
api.Use(func(ctx context.Context) {
|
api.Use(func(ctx context.Context) {
|
||||||
ctx.ViewLayout(tmplLayoutFile)
|
ctx.ViewLayout(tmplLayoutFile)
|
||||||
|
@ -811,14 +803,14 @@ func (api *APIBuilder) Layout(tmplLayoutFile string) Party {
|
||||||
}
|
}
|
||||||
|
|
||||||
// joinHandlers uses to create a copy of all Handlers and return them in order to use inside the node
|
// joinHandlers uses to create a copy of all Handlers and return them in order to use inside the node
|
||||||
func joinHandlers(Handlers1 context.Handlers, Handlers2 context.Handlers) context.Handlers {
|
func joinHandlers(h1 context.Handlers, h2 context.Handlers) context.Handlers {
|
||||||
nowLen := len(Handlers1)
|
nowLen := len(h1)
|
||||||
totalLen := nowLen + len(Handlers2)
|
totalLen := nowLen + len(h2)
|
||||||
// create a new slice of Handlers in order to store all handlers, the already handlers(Handlers) and the new
|
// create a new slice of Handlers in order to merge the "h1" and "h2"
|
||||||
newHandlers := make(context.Handlers, totalLen)
|
newHandlers := make(context.Handlers, totalLen)
|
||||||
// copy the already Handlers to the just created
|
// copy the already Handlers to the just created
|
||||||
copy(newHandlers, Handlers1)
|
copy(newHandlers, h1)
|
||||||
// start from there we finish, and store the new Handlers too
|
// start from there we finish, and store the new Handlers too
|
||||||
copy(newHandlers[nowLen:], Handlers2)
|
copy(newHandlers[nowLen:], h2)
|
||||||
return newHandlers
|
return newHandlers
|
||||||
}
|
}
|
||||||
|
|
|
@ -411,7 +411,7 @@ func detectOrWriteContentType(ctx context.Context, name string, content io.ReadS
|
||||||
// content must be seeked to the beginning of the file.
|
// content must be seeked to the beginning of the file.
|
||||||
// The sizeFunc is called at most once. Its error, if any, is sent in the HTTP response.
|
// The sizeFunc is called at most once. Its error, if any, is sent in the HTTP response.
|
||||||
func serveContent(ctx context.Context, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker) (string, int) /* we could use the TransactionErrResult but prefer not to create new objects for each of the errors on static file handlers*/ {
|
func serveContent(ctx context.Context, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker) (string, int) /* we could use the TransactionErrResult but prefer not to create new objects for each of the errors on static file handlers*/ {
|
||||||
setLastModified(ctx, modtime)
|
context.SetLastModified(ctx, modtime)
|
||||||
done, rangeReq := checkPreconditions(ctx, modtime)
|
done, rangeReq := checkPreconditions(ctx, modtime)
|
||||||
if done {
|
if done {
|
||||||
return "", http.StatusNotModified
|
return "", http.StatusNotModified
|
||||||
|
@ -515,6 +515,17 @@ func serveContent(ctx context.Context, name string, modtime time.Time, sizeFunc
|
||||||
return "", code
|
return "", code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func etagEmptyOrStrongMatch(rangeValue string, etagValue string) bool {
|
||||||
|
etag, _ := scanETag(rangeValue)
|
||||||
|
if etag != "" {
|
||||||
|
if etagStrongMatch(etag, etagValue) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// scanETag determines if a syntactically valid ETag is present at s. If so,
|
// scanETag determines if a syntactically valid ETag is present at s. If so,
|
||||||
// the ETag and remaining text after consuming ETag is returned. Otherwise,
|
// the ETag and remaining text after consuming ETag is returned. Otherwise,
|
||||||
// it returns "", "".
|
// it returns "", "".
|
||||||
|
@ -595,22 +606,6 @@ func checkIfMatch(ctx context.Context) condResult {
|
||||||
return condFalse
|
return condFalse
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkIfUnmodifiedSince(ctx context.Context, modtime time.Time) condResult {
|
|
||||||
ius := ctx.GetHeader("If-Unmodified-Since")
|
|
||||||
if ius == "" || isZeroTime(modtime) {
|
|
||||||
return condNone
|
|
||||||
}
|
|
||||||
if t, err := http.ParseTime(ius); err == nil {
|
|
||||||
// The Date-Modified header truncates sub-second precision, so
|
|
||||||
// use mtime < t+1s instead of mtime <= t to check for unmodified.
|
|
||||||
if modtime.Before(t.Add(1 * time.Second)) {
|
|
||||||
return condTrue
|
|
||||||
}
|
|
||||||
return condFalse
|
|
||||||
}
|
|
||||||
return condNone
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkIfNoneMatch(ctx context.Context) condResult {
|
func checkIfNoneMatch(ctx context.Context) condResult {
|
||||||
inm := ctx.GetHeader("If-None-Match")
|
inm := ctx.GetHeader("If-None-Match")
|
||||||
if inm == "" {
|
if inm == "" {
|
||||||
|
@ -640,86 +635,6 @@ func checkIfNoneMatch(ctx context.Context) condResult {
|
||||||
return condTrue
|
return condTrue
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkIfModifiedSince(ctx context.Context, modtime time.Time) condResult {
|
|
||||||
if ctx.Method() != http.MethodGet && ctx.Method() != http.MethodHead {
|
|
||||||
return condNone
|
|
||||||
}
|
|
||||||
ims := ctx.GetHeader("If-Modified-Since")
|
|
||||||
if ims == "" || isZeroTime(modtime) {
|
|
||||||
return condNone
|
|
||||||
}
|
|
||||||
t, err := http.ParseTime(ims)
|
|
||||||
if err != nil {
|
|
||||||
return condNone
|
|
||||||
}
|
|
||||||
// The Date-Modified header truncates sub-second precision, so
|
|
||||||
// use mtime < t+1s instead of mtime <= t to check for unmodified.
|
|
||||||
if modtime.Before(t.Add(1 * time.Second)) {
|
|
||||||
return condFalse
|
|
||||||
}
|
|
||||||
return condTrue
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkIfRange(ctx context.Context, modtime time.Time) condResult {
|
|
||||||
if ctx.Method() != http.MethodGet {
|
|
||||||
return condNone
|
|
||||||
}
|
|
||||||
ir := ctx.GetHeader("If-Range")
|
|
||||||
if ir == "" {
|
|
||||||
return condNone
|
|
||||||
}
|
|
||||||
etag, _ := scanETag(ir)
|
|
||||||
if etag != "" {
|
|
||||||
if etagStrongMatch(etag, ctx.ResponseWriter().Header().Get("Etag")) {
|
|
||||||
return condTrue
|
|
||||||
}
|
|
||||||
return condFalse
|
|
||||||
|
|
||||||
}
|
|
||||||
// The If-Range value is typically the ETag value, but it may also be
|
|
||||||
// the modtime date. See golang.org/issue/8367.
|
|
||||||
if modtime.IsZero() {
|
|
||||||
return condFalse
|
|
||||||
}
|
|
||||||
t, err := http.ParseTime(ir)
|
|
||||||
if err != nil {
|
|
||||||
return condFalse
|
|
||||||
}
|
|
||||||
if t.Unix() == modtime.Unix() {
|
|
||||||
return condTrue
|
|
||||||
}
|
|
||||||
return condFalse
|
|
||||||
}
|
|
||||||
|
|
||||||
var unixEpochTime = time.Unix(0, 0)
|
|
||||||
|
|
||||||
// isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
|
|
||||||
func isZeroTime(t time.Time) bool {
|
|
||||||
return t.IsZero() || t.Equal(unixEpochTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setLastModified(ctx context.Context, modtime time.Time) {
|
|
||||||
if !isZeroTime(modtime) {
|
|
||||||
ctx.Header(lastModifiedHeaderKey, modtime.UTC().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeNotModified(ctx context.Context) {
|
|
||||||
// RFC 7232 section 4.1:
|
|
||||||
// a sender SHOULD NOT generate representation metadata other than the
|
|
||||||
// above listed fields unless said metadata exists for the purpose of
|
|
||||||
// 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, "Last-Modified")
|
|
||||||
}
|
|
||||||
ctx.StatusCode(http.StatusNotModified)
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkPreconditions evaluates request preconditions and reports whether a precondition
|
// checkPreconditions evaluates request preconditions and reports whether a precondition
|
||||||
// resulted in sending StatusNotModified or StatusPreconditionFailed.
|
// resulted in sending StatusNotModified or StatusPreconditionFailed.
|
||||||
func checkPreconditions(ctx context.Context, modtime time.Time) (done bool, rangeHeader string) {
|
func checkPreconditions(ctx context.Context, modtime time.Time) (done bool, rangeHeader string) {
|
||||||
|
@ -736,28 +651,72 @@ func checkPreconditions(ctx context.Context, modtime time.Time) (done bool, rang
|
||||||
switch checkIfNoneMatch(ctx) {
|
switch checkIfNoneMatch(ctx) {
|
||||||
case condFalse:
|
case condFalse:
|
||||||
if ctx.Method() == http.MethodGet || ctx.Method() == http.MethodHead {
|
if ctx.Method() == http.MethodGet || ctx.Method() == http.MethodHead {
|
||||||
writeNotModified(ctx)
|
context.WriteNotModified(ctx)
|
||||||
return true, ""
|
return true, ""
|
||||||
}
|
}
|
||||||
ctx.StatusCode(http.StatusPreconditionFailed)
|
ctx.StatusCode(http.StatusPreconditionFailed)
|
||||||
return true, ""
|
return true, ""
|
||||||
|
|
||||||
case condNone:
|
case condNone:
|
||||||
if checkIfModifiedSince(ctx, modtime) == condFalse {
|
if modified, err := context.CheckIfModifiedSince(ctx, modtime); !modified && err == nil {
|
||||||
writeNotModified(ctx)
|
context.WriteNotModified(ctx)
|
||||||
return true, ""
|
return true, ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rangeHeader = ctx.GetHeader("Range")
|
rangeHeader = ctx.GetHeader("Range")
|
||||||
if rangeHeader != "" {
|
if rangeHeader != "" {
|
||||||
if checkIfRange(ctx, modtime) == condFalse {
|
if checkIfRange(ctx, etagEmptyOrStrongMatch, modtime) == condFalse {
|
||||||
rangeHeader = ""
|
rangeHeader = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, rangeHeader
|
return false, rangeHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkIfUnmodifiedSince(ctx context.Context, modtime time.Time) condResult {
|
||||||
|
ius := ctx.GetHeader("If-Unmodified-Since")
|
||||||
|
if ius == "" || context.IsZeroTime(modtime) {
|
||||||
|
return condNone
|
||||||
|
}
|
||||||
|
if t, err := context.ParseTime(ctx, ius); err == nil {
|
||||||
|
// The Date-Modified header truncates sub-second precision, so
|
||||||
|
// use mtime < t+1s instead of mtime <= t to check for unmodified.
|
||||||
|
if modtime.Before(t.Add(1 * time.Second)) {
|
||||||
|
return condTrue
|
||||||
|
}
|
||||||
|
return condFalse
|
||||||
|
}
|
||||||
|
return condNone
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkIfRange(ctx context.Context, etagEmptyOrStrongMatch func(ifRangeValue string, etagValue string) bool, modtime time.Time) condResult {
|
||||||
|
if ctx.Method() != http.MethodGet {
|
||||||
|
return condNone
|
||||||
|
}
|
||||||
|
ir := ctx.GetHeader("If-Range")
|
||||||
|
if ir == "" {
|
||||||
|
return condNone
|
||||||
|
}
|
||||||
|
|
||||||
|
if etagEmptyOrStrongMatch(ir, ctx.GetHeader("Etag")) {
|
||||||
|
return condTrue
|
||||||
|
}
|
||||||
|
|
||||||
|
// The If-Range value is typically the ETag value, but it may also be
|
||||||
|
// the modtime date. See golang.org/issue/8367.
|
||||||
|
if modtime.IsZero() {
|
||||||
|
return condFalse
|
||||||
|
}
|
||||||
|
t, err := context.ParseTime(ctx, ir)
|
||||||
|
if err != nil {
|
||||||
|
return condFalse
|
||||||
|
}
|
||||||
|
if t.Unix() == modtime.Unix() {
|
||||||
|
return condTrue
|
||||||
|
}
|
||||||
|
return condFalse
|
||||||
|
}
|
||||||
|
|
||||||
// name is '/'-separated, not filepath.Separator.
|
// name is '/'-separated, not filepath.Separator.
|
||||||
func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bool, showList bool, gzip bool) (string, int) {
|
func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bool, showList bool, gzip bool) (string, int) {
|
||||||
const indexPage = "/index.html"
|
const indexPage = "/index.html"
|
||||||
|
@ -826,8 +785,8 @@ func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bo
|
||||||
if !showList {
|
if !showList {
|
||||||
return "", http.StatusForbidden
|
return "", http.StatusForbidden
|
||||||
}
|
}
|
||||||
if checkIfModifiedSince(ctx, d.ModTime()) == condFalse {
|
if modified, err := context.CheckIfModifiedSince(ctx, d.ModTime()); !modified && err == nil {
|
||||||
writeNotModified(ctx)
|
context.WriteNotModified(ctx)
|
||||||
return "", http.StatusNotModified
|
return "", http.StatusNotModified
|
||||||
}
|
}
|
||||||
ctx.Header("Last-Modified", d.ModTime().UTC().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat()))
|
ctx.Header("Last-Modified", d.ModTime().UTC().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat()))
|
||||||
|
@ -842,7 +801,7 @@ func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bo
|
||||||
}
|
}
|
||||||
|
|
||||||
// else, set the last modified as "serveContent" does.
|
// else, set the last modified as "serveContent" does.
|
||||||
setLastModified(ctx, d.ModTime())
|
context.SetLastModified(ctx, d.ModTime())
|
||||||
|
|
||||||
// write the file to the response writer.
|
// write the file to the response writer.
|
||||||
contents, err := ioutil.ReadAll(f)
|
contents, err := ioutil.ReadAll(f)
|
||||||
|
|
|
@ -211,15 +211,19 @@ type Party interface {
|
||||||
// Returns the GET *Route.
|
// Returns the GET *Route.
|
||||||
StaticWeb(requestPath string, systemPath string) *Route
|
StaticWeb(requestPath string, systemPath string) *Route
|
||||||
|
|
||||||
// Layout oerrides the parent template layout with a more specific layout for this Party
|
// Layout overrides the parent template layout with a more specific layout for this Party.
|
||||||
// returns this Party, to continue as normal
|
// It returns the current Party.
|
||||||
|
//
|
||||||
|
// The "tmplLayoutFile" should be a relative path to the templates dir.
|
||||||
// Usage:
|
// Usage:
|
||||||
|
//
|
||||||
// app := iris.New()
|
// app := iris.New()
|
||||||
|
// app.RegisterView(iris.$VIEW_ENGINE("./views", ".$extension"))
|
||||||
// my := app.Party("/my").Layout("layouts/mylayout.html")
|
// my := app.Party("/my").Layout("layouts/mylayout.html")
|
||||||
// {
|
// my.Get("/", func(ctx iris.Context) {
|
||||||
// my.Get("/", func(ctx context.Context) {
|
// ctx.View("page1.html")
|
||||||
// ctx.MustRender("page1.html", nil)
|
|
||||||
// })
|
// })
|
||||||
// }
|
//
|
||||||
|
// Examples: https://github.com/kataras/iris/tree/master/_examples/view
|
||||||
Layout(tmplLayoutFile string) Party
|
Layout(tmplLayoutFile string) Party
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,22 @@ func GetCustomStructWithError(ctx iris.Context) (s testCustomStruct, err error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type err struct {
|
||||||
|
Status int `json:"status_code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e err) Dispatch(ctx iris.Context) {
|
||||||
|
// write the status code based on the err's StatusCode.
|
||||||
|
ctx.StatusCode(e.Status)
|
||||||
|
// send to the client the whole object as json
|
||||||
|
ctx.JSON(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCustomErrorAsDispatcher() err {
|
||||||
|
return err{iris.StatusBadRequest, "this is my error as json"}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFuncResult(t *testing.T) {
|
func TestFuncResult(t *testing.T) {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
h := New()
|
h := New()
|
||||||
|
@ -102,6 +118,7 @@ func TestFuncResult(t *testing.T) {
|
||||||
app.Get("/custom/struct/with/status/not/ok", h.Handler(GetCustomStructWithStatusNotOk))
|
app.Get("/custom/struct/with/status/not/ok", h.Handler(GetCustomStructWithStatusNotOk))
|
||||||
app.Get("/custom/struct/with/content/type", h.Handler(GetCustomStructWithContentType))
|
app.Get("/custom/struct/with/content/type", h.Handler(GetCustomStructWithContentType))
|
||||||
app.Get("/custom/struct/with/error", h.Handler(GetCustomStructWithError))
|
app.Get("/custom/struct/with/error", h.Handler(GetCustomStructWithError))
|
||||||
|
app.Get("/custom/error/as/dispatcher", h.Handler(GetCustomErrorAsDispatcher))
|
||||||
|
|
||||||
e := httptest.New(t, app)
|
e := httptest.New(t, app)
|
||||||
|
|
||||||
|
@ -149,4 +166,10 @@ func TestFuncResult(t *testing.T) {
|
||||||
// the content should be not JSON it should be the status code's text
|
// the content should be not JSON it should be the status code's text
|
||||||
// it will fire the error's text
|
// it will fire the error's text
|
||||||
Body().Equal("omit return of testCustomStruct and fire error")
|
Body().Equal("omit return of testCustomStruct and fire error")
|
||||||
|
|
||||||
|
e.GET("/custom/error/as/dispatcher").Expect().
|
||||||
|
Status(iris.StatusBadRequest). // the default status code if error is not nil
|
||||||
|
// the content should be not JSON it should be the status code's text
|
||||||
|
// it will fire the error's text
|
||||||
|
JSON().Equal(err{iris.StatusBadRequest, "this is my error as json"})
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,12 @@ func New(t *testing.T, app *iris.Application, setters ...OptionSetter) *httpexpe
|
||||||
// set the logger or disable it (default) and disable the updater (for any case).
|
// set the logger or disable it (default) and disable the updater (for any case).
|
||||||
app.Configure(iris.WithoutVersionChecker)
|
app.Configure(iris.WithoutVersionChecker)
|
||||||
app.Logger().SetLevel(conf.LogLevel)
|
app.Logger().SetLevel(conf.LogLevel)
|
||||||
app.Build()
|
if err := app.Build(); err != nil {
|
||||||
|
if conf.Debug && (conf.LogLevel == "disable" || conf.LogLevel == "disabled") {
|
||||||
|
app.Logger().Println(err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
testConfiguration := httpexpect.Config{
|
testConfiguration := httpexpect.Config{
|
||||||
BaseURL: conf.URL,
|
BaseURL: conf.URL,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user