mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +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
|
||||
|
||||
## 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
|
||||
|
||||
```sh
|
||||
|
@ -18,7 +26,7 @@ go get -u github.com/kataras/iris
|
|||
|
||||
## 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/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" />
|
||||
|
||||
[![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.
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
||||
[![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.
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
||||
[![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.
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
||||
[![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开发框架。
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
func TestCasbinWrapper(t *testing.T) {
|
||||
app := newApp()
|
||||
e := httptest.New(t, app, httptest.Debug(true))
|
||||
e := httptest.New(t, app)
|
||||
|
||||
type ttcasbin struct {
|
||||
username string
|
||||
|
@ -43,7 +43,6 @@ func TestCasbinWrapper(t *testing.T) {
|
|||
check(e, tt.method, tt.path, tt.username, tt.status)
|
||||
}
|
||||
|
||||
println("ADMIN ROLES")
|
||||
ttAdmin := []ttcasbin{
|
||||
{"cathrin", "/dataset1/item", "GET", 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)
|
||||
}
|
||||
|
||||
println("ADMIN ROLE FOR cathrin DELETED")
|
||||
Enforcer.DeleteRolesForUser("cathrin")
|
||||
|
||||
ttAdminDeleted := []ttcasbin{
|
||||
|
|
|
@ -42,7 +42,7 @@ func getSignupForm(ctx iris.Context) {
|
|||
// views/signup.html just needs a {{ .csrfField }} template tag for
|
||||
// csrf.TemplateField to inject the CSRF token into. Easy!
|
||||
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
|
||||
// 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 {
|
||||
req := e.GET(test.path)
|
||||
// req.WithURL("http://www." + root)
|
||||
|
||||
req.Expect().Status(httptest.StatusOK).Body().Equal(test.response)
|
||||
e.GET(test.path).Expect().Status(httptest.StatusOK).Body().Equal(test.response)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,19 +30,19 @@ func newApp() *iris.Application {
|
|||
|
||||
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
|
||||
www.Get("/hi", func(ctx iris.Context) {
|
||||
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,
|
||||
// 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"
|
||||
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 {
|
||||
|
||||
req := e.Request(test.method, test.path)
|
||||
if subdomain := test.subdomain; subdomain != "" {
|
||||
req.WithURL("http://" + subdomain + "." + host)
|
||||
req = req.WithURL("http://" + subdomain + "." + host)
|
||||
}
|
||||
|
||||
req.Expect().
|
||||
|
|
|
@ -891,18 +891,6 @@ type Context interface {
|
|||
|
||||
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)
|
||||
// and executes the first handler,
|
||||
// handlers should not be empty.
|
||||
|
@ -1159,17 +1147,38 @@ func (ctx *context) HandlerName() string {
|
|||
return HandlerName(ctx.handlers[ctx.currentHandlerIndex])
|
||||
}
|
||||
|
||||
// Do sets the handler index to zero, executes the first handler
|
||||
// and the rest of the Handlers if ctx.Next() was called.
|
||||
// func (ctx *context) Do() {
|
||||
// ctx.currentHandlerIndex = 0
|
||||
// ctx.handlers[0](ctx) // it calls this *context
|
||||
// } // -> replaced with inline on router.go
|
||||
// Next is the function that executed when `ctx.Next()` is called.
|
||||
// It can be changed to a customized one if needed (very advanced usage).
|
||||
//
|
||||
// See `DefaultNext` for more information about this and why it's exported like this.
|
||||
var Next = DefaultNext ///TODO: add an example for this usecase, i.e describe handlers and skip only file handlers.
|
||||
|
||||
// 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,
|
||||
// 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)
|
||||
Next(ctx)
|
||||
}
|
||||
|
@ -2046,29 +2055,111 @@ var (
|
|||
varyHeaderKey = "Vary"
|
||||
)
|
||||
|
||||
// staticCachePassed checks the IfModifiedSince header and
|
||||
// returns true if (client-side) duration has expired
|
||||
func (ctx *context) staticCachePassed(modtime time.Time) bool {
|
||||
if t, err := time.Parse(ctx.Application().ConfigurationReadOnly().GetTimeFormat(), ctx.GetHeader(ifModifiedSinceHeaderKey)); err == nil && modtime.Before(t.Add(StaticCacheDuration)) {
|
||||
ctx.writer.Header().Del(contentTypeHeaderKey)
|
||||
ctx.writer.Header().Del(contentLengthHeaderKey)
|
||||
ctx.StatusCode(http.StatusNotModified)
|
||||
return true
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 false
|
||||
|
||||
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
|
||||
// which is refreshed every package-level `StaticCacheDuration` field.
|
||||
func (ctx *context) WriteWithExpiration(body []byte, modtime time.Time) (int, error) {
|
||||
|
||||
if ctx.staticCachePassed(modtime) {
|
||||
if modified, err := CheckIfModifiedSince(ctx, modtime); !modified && err == nil {
|
||||
WriteNotModified(ctx)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
modtimeFormatted := modtime.UTC().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())
|
||||
ctx.Header(lastModifiedHeaderKey, modtimeFormatted)
|
||||
|
||||
SetLastModified(ctx, modtime)
|
||||
return ctx.writer.Write(body)
|
||||
}
|
||||
|
||||
|
@ -2658,16 +2749,13 @@ const (
|
|||
// You can define your own "Content-Type" header also, after this function call
|
||||
// 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 {
|
||||
if t, err := time.Parse(ctx.Application().ConfigurationReadOnly().GetTimeFormat(), ctx.GetHeader(ifModifiedSinceHeaderKey)); err == nil && modtime.Before(t.Add(1*time.Second)) {
|
||||
ctx.writer.Header().Del(contentTypeHeaderKey)
|
||||
ctx.writer.Header().Del(contentLengthHeaderKey)
|
||||
ctx.StatusCode(http.StatusNotModified)
|
||||
if modified, err := CheckIfModifiedSince(ctx, modtime); !modified && err == nil {
|
||||
WriteNotModified(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx.ContentType(filename)
|
||||
ctx.writer.Header().Set(lastModifiedHeaderKey, modtime.UTC().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat()))
|
||||
ctx.StatusCode(http.StatusOK)
|
||||
SetLastModified(ctx, modtime)
|
||||
var out io.Writer
|
||||
if gzipCompression && ctx.ClientSupportsGzip() {
|
||||
ctx.writer.Header().Add(varyHeaderKey, acceptEncodingHeaderKey)
|
||||
|
@ -2680,7 +2768,7 @@ func (ctx *context) ServeContent(content io.ReadSeeker, filename string, modtime
|
|||
out = ctx.writer
|
||||
}
|
||||
_, 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)
|
||||
|
|
|
@ -629,7 +629,6 @@ func (api *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
|
|||
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.
|
||||
cacheFav := make([]byte, fi.Size())
|
||||
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()))
|
||||
return nil
|
||||
}
|
||||
modtime := ""
|
||||
|
||||
modtime := time.Now()
|
||||
cType := TypeByFilename(favPath)
|
||||
h := func(ctx context.Context) {
|
||||
if modtime == "" {
|
||||
modtime = fi.ModTime().UTC().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())
|
||||
}
|
||||
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.ContentType(cType)
|
||||
if _, err := ctx.WriteWithExpiration(cacheFav, modtime); err != nil {
|
||||
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) {
|
||||
h(ctx)
|
||||
if ctx.GetStatusCode() >= 200 && ctx.GetStatusCode() < 400 {
|
||||
// re-check the content type here for any case,
|
||||
// although the new code does it automatically but it's good to have it here.
|
||||
if _, exists := ctx.ResponseWriter().Header()["Content-Type"]; !exists {
|
||||
if fname := ctx.Params().Get(paramName); fname != "" {
|
||||
cType := TypeByFilename(fname)
|
||||
ctx.ContentType(cType)
|
||||
}
|
||||
}
|
||||
}
|
||||
// if ctx.GetStatusCode() >= 200 && ctx.GetStatusCode() < 400 {
|
||||
// // re-check the content type here for any case,
|
||||
// // although the new code does it automatically but it's good to have it here.
|
||||
// if _, exists := ctx.ResponseWriter().Header()["Content-Type"]; !exists {
|
||||
// if fname := ctx.Params().Get(paramName); fname != "" {
|
||||
// cType := TypeByFilename(fname)
|
||||
// ctx.ContentType(cType)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
requestPath = joinPath(requestPath, WildcardParam(paramName))
|
||||
|
@ -791,16 +779,20 @@ func (api *APIBuilder) FireErrorCode(ctx context.Context) {
|
|||
api.errorCodeHandlers.Fire(ctx)
|
||||
}
|
||||
|
||||
// Layout oerrides the parent template layout with a more specific layout for this Party
|
||||
// returns this Party, to continue as normal
|
||||
// Layout overrides the parent template layout with a more specific layout for this Party.
|
||||
// It returns the current Party.
|
||||
//
|
||||
// The "tmplLayoutFile" should be a relative path to the templates dir.
|
||||
// Usage:
|
||||
//
|
||||
// app := iris.New()
|
||||
// app.RegisterView(iris.$VIEW_ENGINE("./views", ".$extension"))
|
||||
// my := app.Party("/my").Layout("layouts/mylayout.html")
|
||||
// {
|
||||
// my.Get("/", func(ctx context.Context) {
|
||||
// ctx.MustRender("page1.html", nil)
|
||||
// })
|
||||
// }
|
||||
// my.Get("/", func(ctx iris.Context) {
|
||||
// ctx.View("page1.html")
|
||||
// })
|
||||
//
|
||||
// Examples: https://github.com/kataras/iris/tree/master/_examples/view
|
||||
func (api *APIBuilder) Layout(tmplLayoutFile string) Party {
|
||||
api.Use(func(ctx context.Context) {
|
||||
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
|
||||
func joinHandlers(Handlers1 context.Handlers, Handlers2 context.Handlers) context.Handlers {
|
||||
nowLen := len(Handlers1)
|
||||
totalLen := nowLen + len(Handlers2)
|
||||
// create a new slice of Handlers in order to store all handlers, the already handlers(Handlers) and the new
|
||||
func joinHandlers(h1 context.Handlers, h2 context.Handlers) context.Handlers {
|
||||
nowLen := len(h1)
|
||||
totalLen := nowLen + len(h2)
|
||||
// create a new slice of Handlers in order to merge the "h1" and "h2"
|
||||
newHandlers := make(context.Handlers, totalLen)
|
||||
//copy the already Handlers to the just created
|
||||
copy(newHandlers, Handlers1)
|
||||
//start from there we finish, and store the new Handlers too
|
||||
copy(newHandlers[nowLen:], Handlers2)
|
||||
// copy the already Handlers to the just created
|
||||
copy(newHandlers, h1)
|
||||
// start from there we finish, and store the new Handlers too
|
||||
copy(newHandlers[nowLen:], h2)
|
||||
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.
|
||||
// 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*/ {
|
||||
setLastModified(ctx, modtime)
|
||||
context.SetLastModified(ctx, modtime)
|
||||
done, rangeReq := checkPreconditions(ctx, modtime)
|
||||
if done {
|
||||
return "", http.StatusNotModified
|
||||
|
@ -515,6 +515,17 @@ func serveContent(ctx context.Context, name string, modtime time.Time, sizeFunc
|
|||
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,
|
||||
// the ETag and remaining text after consuming ETag is returned. Otherwise,
|
||||
// it returns "", "".
|
||||
|
@ -595,22 +606,6 @@ func checkIfMatch(ctx context.Context) condResult {
|
|||
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 {
|
||||
inm := ctx.GetHeader("If-None-Match")
|
||||
if inm == "" {
|
||||
|
@ -640,86 +635,6 @@ func checkIfNoneMatch(ctx context.Context) condResult {
|
|||
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
|
||||
// resulted in sending StatusNotModified or StatusPreconditionFailed.
|
||||
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) {
|
||||
case condFalse:
|
||||
if ctx.Method() == http.MethodGet || ctx.Method() == http.MethodHead {
|
||||
writeNotModified(ctx)
|
||||
context.WriteNotModified(ctx)
|
||||
return true, ""
|
||||
}
|
||||
ctx.StatusCode(http.StatusPreconditionFailed)
|
||||
return true, ""
|
||||
|
||||
case condNone:
|
||||
if checkIfModifiedSince(ctx, modtime) == condFalse {
|
||||
writeNotModified(ctx)
|
||||
if modified, err := context.CheckIfModifiedSince(ctx, modtime); !modified && err == nil {
|
||||
context.WriteNotModified(ctx)
|
||||
return true, ""
|
||||
}
|
||||
}
|
||||
|
||||
rangeHeader = ctx.GetHeader("Range")
|
||||
if rangeHeader != "" {
|
||||
if checkIfRange(ctx, modtime) == condFalse {
|
||||
if checkIfRange(ctx, etagEmptyOrStrongMatch, modtime) == condFalse {
|
||||
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.
|
||||
func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bool, showList bool, gzip bool) (string, int) {
|
||||
const indexPage = "/index.html"
|
||||
|
@ -826,8 +785,8 @@ func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bo
|
|||
if !showList {
|
||||
return "", http.StatusForbidden
|
||||
}
|
||||
if checkIfModifiedSince(ctx, d.ModTime()) == condFalse {
|
||||
writeNotModified(ctx)
|
||||
if modified, err := context.CheckIfModifiedSince(ctx, d.ModTime()); !modified && err == nil {
|
||||
context.WriteNotModified(ctx)
|
||||
return "", http.StatusNotModified
|
||||
}
|
||||
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.
|
||||
setLastModified(ctx, d.ModTime())
|
||||
context.SetLastModified(ctx, d.ModTime())
|
||||
|
||||
// write the file to the response writer.
|
||||
contents, err := ioutil.ReadAll(f)
|
||||
|
|
|
@ -211,15 +211,19 @@ type Party interface {
|
|||
// Returns the GET *Route.
|
||||
StaticWeb(requestPath string, systemPath string) *Route
|
||||
|
||||
// Layout oerrides the parent template layout with a more specific layout for this Party
|
||||
// returns this Party, to continue as normal
|
||||
// Layout overrides the parent template layout with a more specific layout for this Party.
|
||||
// It returns the current Party.
|
||||
//
|
||||
// The "tmplLayoutFile" should be a relative path to the templates dir.
|
||||
// Usage:
|
||||
//
|
||||
// app := iris.New()
|
||||
// app.RegisterView(iris.$VIEW_ENGINE("./views", ".$extension"))
|
||||
// my := app.Party("/my").Layout("layouts/mylayout.html")
|
||||
// {
|
||||
// my.Get("/", func(ctx context.Context) {
|
||||
// ctx.MustRender("page1.html", nil)
|
||||
// })
|
||||
// }
|
||||
// my.Get("/", func(ctx iris.Context) {
|
||||
// ctx.View("page1.html")
|
||||
// })
|
||||
//
|
||||
// Examples: https://github.com/kataras/iris/tree/master/_examples/view
|
||||
Layout(tmplLayoutFile string) Party
|
||||
}
|
||||
|
|
|
@ -81,6 +81,22 @@ func GetCustomStructWithError(ctx iris.Context) (s testCustomStruct, err error)
|
|||
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) {
|
||||
app := iris.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/content/type", h.Handler(GetCustomStructWithContentType))
|
||||
app.Get("/custom/struct/with/error", h.Handler(GetCustomStructWithError))
|
||||
app.Get("/custom/error/as/dispatcher", h.Handler(GetCustomErrorAsDispatcher))
|
||||
|
||||
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
|
||||
// it will fire the error's text
|
||||
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).
|
||||
app.Configure(iris.WithoutVersionChecker)
|
||||
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{
|
||||
BaseURL: conf.URL,
|
||||
|
|
Loading…
Reference in New Issue
Block a user