diff --git a/_benchmarks/iris-mvc-templates/main.go b/_benchmarks/iris-mvc-templates/main.go
index cfd46558..c743e15a 100644
--- a/_benchmarks/iris-mvc-templates/main.go
+++ b/_benchmarks/iris-mvc-templates/main.go
@@ -19,7 +19,7 @@ const (
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html").Layout("shared/layout.html"))
- app.StaticWeb("/public", publicDir)
+ app.HandleDir("/public", publicDir)
app.OnAnyErrorCode(onError)
mvc.New(app).Handle(new(controllers.HomeController))
diff --git a/_examples/README.md b/_examples/README.md
index 92408f26..a8f5098c 100644
--- a/_examples/README.md
+++ b/_examples/README.md
@@ -149,7 +149,7 @@ Navigate through examples for a better understanding.
- [Write your own custom parameter types](routing/macros/main.go)
- [Reverse routing](routing/reverse/main.go)
- [Custom Router (high-level)](routing/custom-high-level-router/main.go)
-- [Custom Wrapper](routing/custom-wrapper/main.go)
+- [Custom Wrapper](routing/custom-wrapper/main.go) **UPDATED**
- Custom Context
* [method overriding](routing/custom-context/method-overriding/main.go)
* [new implementation](routing/custom-context/new-implementation/main.go)
@@ -366,14 +366,14 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her
### File Server
- [Favicon](file-server/favicon/main.go)
-- [Basic](file-server/basic/main.go)
-- [Embedding Files Into App Executable File](file-server/embedding-files-into-app/main.go)
-- [Embedding Gziped Files Into App Executable File](file-server/embedding-gziped-files-into-app/main.go)
+- [Basic](file-server/basic/main.go) **UPDATED**
+- [Embedding Files Into App Executable File](file-server/embedding-files-into-app/main.go) **UPDATED**
+- [Embedding Gziped Files Into App Executable File](file-server/embedding-gziped-files-into-app/main.go) **UPDATED**
- [Send/Force-Download Files](file-server/send-files/main.go)
- Single Page Applications
- * [single Page Application](file-server/single-page-application/basic/main.go)
- * [embedded Single Page Application](file-server/single-page-application/embedded-single-page-application/main.go)
- * [embedded Single Page Application with other routes](file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go)
+ * [single Page Application](file-server/single-page-application/basic/main.go) **UPDATED**
+ * [embedded Single Page Application](file-server/single-page-application/embedded-single-page-application/main.go) **UPDATED**
+ * [embedded Single Page Application with other routes](file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go) **UPDATED**
### How to Read from `context.Request() *http.Request`
diff --git a/_examples/README_ZH.md b/_examples/README_ZH.md
index d8aea7a9..286df607 100644
--- a/_examples/README_ZH.md
+++ b/_examples/README_ZH.md
@@ -10,7 +10,7 @@
### 概览
- [Hello world!](hello-world/main.go)
-- [Hello WebAssemply!](webassembly/basic/main.go) **NEW**
+- [Hello WebAssemply!](webassembly/basic/main.go)
- [基础](overview/main.go)
- [教程: 在线人数](tutorial/online-visitors/main.go)
- [教程: 一个“待完成”MVC Application基于Iris和Vue.js](https://hackernoon.com/a-todo-mvc-application-using-iris-and-vue-js-5019ff870064)
@@ -21,7 +21,7 @@
- [教程: DropzoneJS 上传](tutorial/dropzonejs)
- [教程: Caddy 服务器使用](tutorial/caddy)
- [教程: Iris + MongoDB](https://medium.com/go-language/iris-go-framework-mongodb-552e349eab9c)
-- [教程: Apache Kafka的API](tutorial/api-for-apache-kafka) **NEW**
+- [教程: Apache Kafka的API](tutorial/api-for-apache-kafka)
### 目录结构
@@ -105,10 +105,10 @@ app.Get("{root:path}", rootWildcardHandler)
- [自定义 HTTP 错误](routing/http-errors/main.go)
- [动态路径](routing/dynamic-path/main.go)
* [根级通配符路径](routing/dynamic-path/root-wildcard/main.go)
-- [编写你自己的参数类型](routing/macros/main.go) **NEW**
+- [编写你自己的参数类型](routing/macros/main.go)
- [反向路由](routing/reverse/main.go)
-- [自定义路由(高层级)](routing/custom-high-level-router/main.go) **NEW**
-- [自定义包装](routing/custom-wrapper/main.go)
+- [自定义路由(高层级)](routing/custom-high-level-router/main.go)
+- [自定义包装](routing/custom-wrapper/main.go) **更新**
- 自定义上下文
* [方法重写](routing/custom-context/method-overriding/main.go)
* [新实现方式](routing/custom-context/new-implementation/main.go)
@@ -121,8 +121,8 @@ app.Get("{root:path}", rootWildcardHandler)
- [基础](hero/basic/main.go)
- [概览](hero/overview)
-- [Sessions](hero/sessions) **NEW**
-- [另一种依赖注入的例子和通常的较好实践](hero/smart-contract/main.go) **NEW**
+- [Sessions](hero/sessions)
+- [另一种依赖注入的例子和通常的较好实践](hero/smart-contract/main.go) **新**
### MVC 模式
@@ -255,14 +255,14 @@ func(c *ExampleController) Get() string |
参考下面的示例
-- [Hello world](mvc/hello-world/main.go) **UPDATED**
-- [Session Controller](mvc/session-controller/main.go) **UPDATED**
-- [Overview - Plus Repository and Service layers](mvc/overview) **UPDATED**
-- [Login showcase - Plus Repository and Service layers](mvc/login) **UPDATED**
-- [Singleton](mvc/singleton) **NEW**
-- [Websocket Controller](mvc/websocket) **NEW**
-- [Register Middleware](mvc/middleware) **NEW**
-- [Vue.js Todo MVC](tutorial/vuejs-todo-mvc) **NEW**
+- [Hello world](mvc/hello-world/main.go) **更新**
+- [Session Controller](mvc/session-controller/main.go) **更新**
+- [Overview - Plus Repository and Service layers](mvc/overview) **更新**
+- [Login showcase - Plus Repository and Service layers](mvc/login) **更新**
+- [Singleton](mvc/singleton) **新**
+- [Websocket Controller](mvc/websocket) **新**
+- [Register Middleware](mvc/middleware) **新**
+- [Vue.js Todo MVC](tutorial/vuejs-todo-mvc) **新**
### 子域名
@@ -316,14 +316,14 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her
### 文件服务器
- [Favicon](file-server/favicon/main.go)
-- [基础操作](file-server/basic/main.go)
-- [把文件嵌入应用的可执行文件](file-server/embedding-files-into-app/main.go)
-- [嵌入Gzip压缩的文件到可咨询文件](file-server/embedding-gziped-files-into-app/main.go) **NEW**
+- [基础操作](file-server/basic/main.go) **更新**
+- [把文件嵌入应用的可执行文件](file-server/embedding-files-into-app/main.go) **更新**
+- [嵌入Gzip压缩的文件到可咨询文件](file-server/embedding-gziped-files-into-app/main.go) **更新**
- [上传/(强制)下载文件](file-server/send-files/main.go)
- 单页面应用(Single Page Applications)
- * [单页面应用](file-server/single-page-application/basic/main.go)
- * [嵌入式(embedded)单页面应用](file-server/single-page-application/embedded-single-page-application/main.go)
- * [使用额外路由的嵌入式单页面应用](file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go)
+ * [单页面应用](file-server/single-page-application/basic/main.go) **更新**
+ * [嵌入式(embedded)单页面应用](file-server/single-page-application/embedded-single-page-application/main.go) **更新**
+ * [使用额外路由的嵌入式单页面应用](file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go) **更新**
### 如何读取`context.Request() *http.Request`
@@ -346,7 +346,7 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her
- [写入Gzip压缩](http_responsewriter/write-gzip/main.go)
- [流输出Stream Writer](http_responsewriter/stream-writer/main.go)
- [数据传递Transactions](http_responsewriter/transactions/main.go)
-- [SSE](http_responsewriter/sse/main.go) **NEW**
+- [SSE](http_responsewriter/sse/main.go)
- [SSE (third-party package usage for server sent events第三方库SSE)](http_responsewriter/sse-third-party/main.go)
> The `context/context#ResponseWriter()` returns an enchament version of a http.ResponseWriter, these examples show some places where the Context uses this object. Besides that you can use it as you did before iris.
@@ -430,7 +430,7 @@ iris websocket库依赖于它自己的[包](https://github.com/kataras/iris/tree
设计这个包的目的是处理原始websockets,虽然它的API和著名的[socket.io](https://socket.io)很像。我最近读了一片文章,并且对我
决定给iris设计一个**快速的**websocket**限定**包并且不是一个向后传递类socket.io的包。你可以阅读这个链接里的文章https://medium.com/@ivanderbyl/why-you-don-t-need-socket-io-6848f1c871cd。
-- [Basic](websocket/basic) **NEW**
+- [Basic](websocket/basic) **新**
* [Server](websocket/basic/server.go)
* [Go Client](websocket/basic/go-client/client.go)
* [Browser Client](websocket/basic/browser/index.html)
diff --git a/_examples/cache/simple/main.go b/_examples/cache/simple/main.go
index 09f88881..3b3fd88b 100644
--- a/_examples/cache/simple/main.go
+++ b/_examples/cache/simple/main.go
@@ -74,7 +74,7 @@ func writeMarkdown(ctx iris.Context) {
ctx.Markdown(markdownContents)
}
-/* Note that `StaticWeb` does use the browser's disk caching by-default
-therefore, register the cache handler AFTER any StaticWeb calls,
+/* Note that `HandleDir` does use the browser's disk caching by-default
+therefore, register the cache handler AFTER any HandleDir calls,
for a faster solution that server doesn't need to keep track of the response
navigate to https://github.com/kataras/iris/blob/master/_examples/cache/client-side/main.go */
diff --git a/_examples/file-server/basic/assets/app2/app22/just_a_text_no_index.txt b/_examples/file-server/basic/assets/app2/app22/just_a_text_no_index.txt
new file mode 100644
index 00000000..f1cc1278
--- /dev/null
+++ b/_examples/file-server/basic/assets/app2/app22/just_a_text_no_index.txt
@@ -0,0 +1 @@
+just a text.
\ No newline at end of file
diff --git a/_examples/file-server/basic/assets/app2/app2app3/index.html b/_examples/file-server/basic/assets/app2/app2app3/index.html
new file mode 100644
index 00000000..750f10ba
--- /dev/null
+++ b/_examples/file-server/basic/assets/app2/app2app3/index.html
@@ -0,0 +1 @@
+
Hello App2App3 index
\ No newline at end of file
diff --git a/_examples/file-server/basic/assets/app2/index.html b/_examples/file-server/basic/assets/app2/index.html
new file mode 100644
index 00000000..8193d822
--- /dev/null
+++ b/_examples/file-server/basic/assets/app2/index.html
@@ -0,0 +1 @@
+Hello App2 index
\ No newline at end of file
diff --git a/_examples/file-server/basic/main.go b/_examples/file-server/basic/main.go
index 09b4faf8..6ce716e8 100644
--- a/_examples/file-server/basic/main.go
+++ b/_examples/file-server/basic/main.go
@@ -4,38 +4,48 @@ import (
"github.com/kataras/iris"
)
-func main() {
+func newApp() *iris.Application {
app := iris.New()
app.Favicon("./assets/favicon.ico")
- // enable gzip, optionally:
- // if used before the `StaticXXX` handlers then
- // the content byte range feature is gone.
- // recommend: turn off for large files especially
- // when server has low memory,
- // turn on for medium-sized files
- // or for large-sized files if they are zipped already,
- // i.e "zippedDir/file.gz"
- //
- // app.Use(iris.Gzip)
-
// first parameter is the request path
// second is the system directory
//
- // app.StaticWeb("/css", "./assets/css")
- // app.StaticWeb("/js", "./assets/js")
- //
- app.StaticWeb("/static", "./assets")
+ // app.HandleDir("/css", "./assets/css")
+ // app.HandleDir("/js", "./assets/js")
+ app.HandleDir("/static", "./assets", iris.DirOptions{
+ // Defaults to "/index.html", if request path is ending with **/*/$IndexName
+ // then it redirects to **/*(/) which another handler is handling it,
+ // that another handler, called index handler, is auto-registered by the framework
+ // if end developer does not managed to handle it by hand.
+ IndexName: "/index.html",
+ // When files should served under compression.
+ Gzip: false,
+ // List the files inside the current requested directory if `IndexName` not found.
+ ShowList: false,
+ // If `ShowList` is true then this function will be used instead of the default one to show the list of files of a current requested directory(dir).
+ // DirList: func(ctx context.Context, dirName string, dir http.File) error { ... }
+ //
+ // Optional validator that loops through each requested resource.
+ // AssetValidator: func(ctx iris.Context, name string) bool { ... }
+ })
+
+ // You can also register any index handler manually, order of registration does not matter:
+ // app.Get("/static", [...custom middleware...], func(ctx iris.Context) {
+ // [...custom code...]
+ // ctx.ServeFile("./assets/index.html", false)
+ // })
+
+ // http://localhost:8080/static
// http://localhost:8080/static/css/main.css
// http://localhost:8080/static/js/jquery-2.1.1.js
// http://localhost:8080/static/favicon.ico
- app.Run(iris.Addr(":8080"))
-
- // Note:
- // Routing doesn't allows something .StaticWeb("/", "./assets")
- //
- // To see how you can wrap the router in order to achieve
- // wildcard on root path, see "single-page-application".
+ return app
+}
+
+func main() {
+ app := newApp()
+ app.Run(iris.Addr(":8080"))
}
diff --git a/_examples/file-server/basic/main_test.go b/_examples/file-server/basic/main_test.go
new file mode 100644
index 00000000..d0aaef82
--- /dev/null
+++ b/_examples/file-server/basic/main_test.go
@@ -0,0 +1,93 @@
+package main
+
+import (
+ "io/ioutil"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/kataras/iris/httptest"
+)
+
+type resource string
+
+func (r resource) contentType() string {
+ switch filepath.Ext(r.String()) {
+ case ".js":
+ return "application/javascript"
+ case ".css":
+ return "text/css"
+ case ".ico":
+ return "image/x-icon"
+ case ".html", "":
+ return "text/html"
+ default:
+ return "text/plain"
+ }
+}
+
+func (r resource) String() string {
+ return string(r)
+}
+
+func (r resource) strip(strip string) string {
+ s := r.String()
+ return strings.TrimPrefix(s, strip)
+}
+
+func (r resource) loadFromBase(dir string) string {
+ filename := r.String()
+
+ filename = r.strip("/static")
+ if filepath.Ext(filename) == "" {
+ // root /.
+ filename = filename + "/index.html"
+ }
+
+ fullpath := filepath.Join(dir, filename)
+
+ b, err := ioutil.ReadFile(fullpath)
+ if err != nil {
+ panic(fullpath + " failed with error: " + err.Error())
+ }
+
+ result := string(b)
+
+ return result
+}
+
+func TestFileServerBasic(t *testing.T) {
+ var urls = []resource{
+ "/static/css/main.css",
+ "/static/js/jquery-2.1.1.js",
+ "/static/favicon.ico",
+ "/static/app2",
+ "/static/app2/app2app3",
+ "/static",
+ }
+
+ app := newApp()
+ // route := app.GetRouteReadOnly("GET/{file:path}")
+ // if route == nil {
+ // app.Logger().Fatalf("expected a route to serve files")
+ // }
+
+ // if expected, got := "./assets", route.StaticDir(); expected != got {
+ // app.Logger().Fatalf("expected route's static directory to be: '%s' but got: '%s'", expected, got)
+ // }
+
+ // if !route.StaticDirContainsIndex() {
+ // app.Logger().Fatalf("epxected ./assets to contain an %s file", "/index.html")
+ // }
+
+ e := httptest.New(t, app)
+ for _, u := range urls {
+ url := u.String()
+ contents := u.loadFromBase("./assets")
+
+ e.GET(url).Expect().
+ Status(httptest.StatusOK).
+ ContentType(u.contentType(), app.ConfigurationReadOnly().GetCharset()).
+ Body().Equal(contents)
+ }
+}
diff --git a/_examples/file-server/embedding-files-into-app/main.go b/_examples/file-server/embedding-files-into-app/main.go
index ac43c3a8..17f94f03 100644
--- a/_examples/file-server/embedding-files-into-app/main.go
+++ b/_examples/file-server/embedding-files-into-app/main.go
@@ -14,8 +14,14 @@ import (
// See `file-server/embedding-gziped-files-into-app` example as well.
func newApp() *iris.Application {
app := iris.New()
+ app.Logger().SetLevel("debug")
- app.StaticEmbedded("/static", "./assets", Asset, AssetNames)
+ app.HandleDir("/static", "./assets", iris.DirOptions{
+ Asset: Asset,
+ AssetInfo: AssetInfo,
+ AssetNames: AssetNames,
+ ShowList: true,
+ })
return app
}
diff --git a/_examples/file-server/embedding-files-into-app/main_test.go b/_examples/file-server/embedding-files-into-app/main_test.go
index f1d5a41a..24421679 100644
--- a/_examples/file-server/embedding-files-into-app/main_test.go
+++ b/_examples/file-server/embedding-files-into-app/main_test.go
@@ -66,12 +66,21 @@ var urls = []resource{
}
// if bindata's values matches with the assets/... contents
-// and secondly if the StaticEmbedded had successfully registered
+// and secondly if the HandleDir had successfully registered
// the routes and gave the correct response.
func TestEmbeddingFilesIntoApp(t *testing.T) {
app := newApp()
e := httptest.New(t, app)
+ route := app.GetRouteReadOnly("GET/static/{file:path}")
+ if route == nil {
+ t.Fatalf("expected a route to serve embedded files")
+ }
+
+ if len(route.StaticSites()) > 0 {
+ t.Fatalf("not expected a static site, the ./assets directory or its subdirectories do not contain any index.html")
+ }
+
if runtime.GOOS != "windows" {
// remove the embedded static favicon for !windows,
// it should be built for unix-specific in order to be work
diff --git a/_examples/file-server/embedding-gziped-files-into-app/main.go b/_examples/file-server/embedding-gziped-files-into-app/main.go
index 944c251d..75640faf 100644
--- a/_examples/file-server/embedding-gziped-files-into-app/main.go
+++ b/_examples/file-server/embedding-gziped-files-into-app/main.go
@@ -16,12 +16,13 @@ import (
func newApp() *iris.Application {
app := iris.New()
- // Note the `GzipAsset` and `GzipAssetNames` are different from `go-bindata`'s `Asset` and `AssetNames,
- // that means that you can use both `go-bindata` and `bindata` tools,
- // the `go-bindata` can be used for the view engine's `Binary` method
- // and the `bindata` with the `StaticEmbeddedGzip` (x8 times faster than the StaticEmbeded with `go-bindata`).
- app.StaticEmbeddedGzip("/static", "./assets", GzipAsset, GzipAssetNames)
-
+ // Note the `GzipAsset` and `GzipAssetNames` are different from `go-bindata`'s `Asset`,
+ // do not set the `Gzip` option to true, it's already managed by the kataras/bindata.
+ app.HandleDir("/static", "./assets", iris.DirOptions{
+ Asset: GzipAsset,
+ AssetInfo: GzipAssetInfo,
+ AssetNames: GzipAssetNames,
+ })
return app
}
diff --git a/_examples/file-server/embedding-gziped-files-into-app/main_test.go b/_examples/file-server/embedding-gziped-files-into-app/main_test.go
index c5b4799b..4518bfdf 100644
--- a/_examples/file-server/embedding-gziped-files-into-app/main_test.go
+++ b/_examples/file-server/embedding-gziped-files-into-app/main_test.go
@@ -67,7 +67,7 @@ var urls = []resource{
}
// if bindata's values matches with the assets/... contents
-// and secondly if the StaticEmbedded had successfully registered
+// and secondly if the HandleDir had successfully registered
// the routes and gave the correct response.
func TestEmbeddingGzipFilesIntoApp(t *testing.T) {
app := newApp()
diff --git a/_examples/file-server/single-page-application/basic/main.go b/_examples/file-server/single-page-application/basic/main.go
index 649f7066..fc173bcd 100644
--- a/_examples/file-server/single-page-application/basic/main.go
+++ b/_examples/file-server/single-page-application/basic/main.go
@@ -20,15 +20,7 @@ func newApp() *iris.Application {
ctx.View("index.html")
})
- // or just serve index.html as it is:
- // app.Get("/{f:path}", func(ctx iris.Context) {
- // ctx.ServeFile("index.html", false)
- // })
-
- assetHandler := app.StaticHandler("./public", false, false)
- // as an alternative of SPA you can take a look at the /routing/dynamic-path/root-wildcard
- // example too
- app.SPA(assetHandler)
+ app.HandleDir("/", "./public")
return app
}
diff --git a/_examples/file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go b/_examples/file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go
index 14140f92..6c3522aa 100644
--- a/_examples/file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go
+++ b/_examples/file-server/single-page-application/embedded-single-page-application-with-other-routes/main.go
@@ -9,11 +9,21 @@ import "github.com/kataras/iris"
func newApp() *iris.Application {
app := iris.New()
- app.OnErrorCode(404, func(ctx iris.Context) {
+ app.OnErrorCode(iris.StatusNotFound, func(ctx iris.Context) {
ctx.Writef("404 not found here")
})
- app.StaticEmbedded("/", "./public", Asset, AssetNames)
+ app.HandleDir("/", "./public", iris.DirOptions{
+ Asset: Asset,
+ AssetInfo: AssetInfo,
+ AssetNames: AssetNames,
+ // IndexName: "index.html", // default.
+ // If you want to show a list of embedded files when inside a directory without an index file:
+ // ShowList: true,
+ // DirList: func(ctx iris.Context, dirName string, f http.File) error {
+ // // [Optional, custom code to show the html list].
+ // }
+ })
// Note:
// if you want a dynamic index page then see the file-server/embedded-single-page-application
diff --git a/_examples/file-server/single-page-application/embedded-single-page-application/main.go b/_examples/file-server/single-page-application/embedded-single-page-application/main.go
index 8c44a3e0..e0b0570a 100644
--- a/_examples/file-server/single-page-application/embedded-single-page-application/main.go
+++ b/_examples/file-server/single-page-application/embedded-single-page-application/main.go
@@ -22,16 +22,11 @@ func newApp() *iris.Application {
ctx.View("index.html")
})
- assetHandler := iris.StaticEmbeddedHandler("./public", Asset, AssetNames, false) // keep that false if you use the `go-bindata` tool.
- // as an alternative of SPA you can take a look at the /routing/dynamic-path/root-wildcard
- // example too
- // or
- // app.StaticEmbedded if you don't want to redirect on index.html and simple serve your SPA app (recommended).
-
- // public/index.html is a dynamic view, it's handlded by root,
- // and we don't want to be visible as a raw data, so we will
- // the return value of `app.SPA` to modify the `IndexNames` by;
- app.SPA(assetHandler).AddIndexName("index.html")
+ app.HandleDir("/", "./public", iris.DirOptions{
+ Asset: Asset,
+ AssetInfo: AssetInfo,
+ AssetNames: AssetNames,
+ })
return app
}
@@ -45,11 +40,3 @@ func main() {
// http://localhost:8080/css/main.css
app.Run(iris.Addr(":8080"))
}
-
-// Note that app.Use/UseGlobal/Done will be executed
-// only to the registered routes like our index (app.Get("/", ..)).
-// The file server is clean, but you can still add middleware to that by wrapping its "assetHandler".
-//
-// With this method, unlike StaticWeb("/" , "./public") which is not working by-design anymore,
-// all custom http errors and all routes are working fine with a file server that is registered
-// to the root path of the server.
diff --git a/_examples/http_request/request-logger/request-logger-file-json/main.go b/_examples/http_request/request-logger/request-logger-file-json/main.go
index 2387de48..9fbe24c6 100644
--- a/_examples/http_request/request-logger/request-logger-file-json/main.go
+++ b/_examples/http_request/request-logger/request-logger-file-json/main.go
@@ -2,19 +2,56 @@ package main
import (
"fmt"
+ "io"
"os"
- "runtime"
"strings"
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/middleware/logger"
-
- "github.com/kataras/golog"
)
const deleteFileOnExit = false
+func newRequestLogger(newWriter io.Writer) iris.Handler {
+ c := logger.Config{}
+
+ // we don't want to use the logger
+ // to log requests to assets and etc
+ c.AddSkipper(func(ctx iris.Context) bool {
+ path := ctx.Path()
+ for _, ext := range excludeExtensions {
+ if strings.HasSuffix(path, ext) {
+ return true
+ }
+ }
+ return false
+ })
+
+ c.LogFuncCtx = func(ctx iris.Context, latency time.Duration) {
+ datetime := time.Now().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())
+ customHandlerMessage := ctx.Values().GetString("log_message")
+
+ file, line := ctx.HandlerFileLine()
+ source := fmt.Sprintf("%s:%d", file, line)
+
+ // this will just append a line without an array of javascript objects, readers of this file should read one line per log javascript object,
+ // however, you can improve it even more, this is just a simple example on how to use the `LogFuncCtx`.
+ jsonStr := fmt.Sprintf(`{"datetime":"%s","level":"%s","source":"%s","latency": "%s","status": %d,"method":"%s","path":"%s","message":"%s"}`,
+ datetime, "INFO", source, latency.String(), ctx.GetStatusCode(), ctx.Method(), ctx.Path(), customHandlerMessage)
+
+ fmt.Fprintln(newWriter, jsonStr)
+ }
+
+ return logger.New(c)
+}
+
+func h(ctx iris.Context) {
+ ctx.Values().Set("log_message", "something to give more info to the request logger")
+
+ ctx.Writef("Hello from %s", ctx.Path())
+}
+
func main() {
app := iris.New()
@@ -26,56 +63,21 @@ func main() {
}
}()
- // Handle the logs by yourself using the `app.Logger#Handle` method.
- // Return true if that handled, otherwise will print to the screen.
- // You can also use the `app.Logger#SetOutput/AddOutput` to change or add
- // multi (io.Writer) outputs if you just want to print the message
- // somewhere else than the terminal screen.
- app.Logger().Handle(func(l *golog.Log) bool {
- _, fn, line, _ := runtime.Caller(5)
-
- var (
- // formatted date string based on the `golog#TimeFormat`, which can be customized.
- // Or use the golog.Log#Time field to get the exact time.Time instance.
- datetime = l.FormatTime()
- // the log's message level.
- level = golog.GetTextForLevel(l.Level, false)
- // the log's message.
- message = l.Message
- // the source code line of where it is called,
- // this can differ on your app, see runtime.Caller(%d).
- source = fmt.Sprintf("%s#%d", fn, line)
- )
-
- // You can always use a custom json structure and json.Marshal and logFile.Write(its result)
- // but it is faster to just build your JSON string by yourself as we do below.
- jsonStr := fmt.Sprintf(`{"datetime":"%s","level":"%s","message":"%s","source":"%s"}`, datetime, level, message, source)
- fmt.Fprintln(logFile, jsonStr)
-
- /* Example output:
- {"datetime":"2018/10/31 13:13","level":"[INFO]","message":"My server started","source":"c:/mygopath/src/github.com/kataras/iris/_examples/http_request/request-logger/request-logger-file-json/main.go#71"}
- */
- return true
- })
-
- r := newRequestLogger()
+ r := newRequestLogger(logFile)
app.Use(r)
app.OnAnyErrorCode(r, func(ctx iris.Context) {
ctx.HTML(" Error: Please try this instead.
")
})
- h := func(ctx iris.Context) {
- ctx.Writef("Hello from %s", ctx.Path())
- }
-
app.Get("/", h)
app.Get("/1", h)
app.Get("/2", h)
- app.Logger().Info("My server started")
+ app.Get("/", h)
+
// http://localhost:8080
// http://localhost:8080/1
// http://localhost:8080/2
@@ -92,29 +94,6 @@ var excludeExtensions = [...]string{
".svg",
}
-func newRequestLogger() iris.Handler {
- c := logger.Config{
- Status: true,
- IP: true,
- Method: true,
- Path: true,
- }
-
- // we don't want to use the logger
- // to log requests to assets and etc
- c.AddSkipper(func(ctx iris.Context) bool {
- path := ctx.Path()
- for _, ext := range excludeExtensions {
- if strings.HasSuffix(path, ext) {
- return true
- }
- }
- return false
- })
-
- return logger.New(c)
-}
-
// get a filename based on the date, file logs works that way the most times
// but these are just a sugar.
func todayFilename() string {
diff --git a/_examples/http_request/request-logger/request-logger-file/main.go b/_examples/http_request/request-logger/request-logger-file/main.go
index dc37f8a1..1fbe5143 100644
--- a/_examples/http_request/request-logger/request-logger-file/main.go
+++ b/_examples/http_request/request-logger/request-logger-file/main.go
@@ -85,10 +85,10 @@ func newRequestLogger() (h iris.Handler, close func() error) {
return err
}
- c.LogFunc = func(now time.Time, latency time.Duration, status, ip, method, path string, message interface{}, headerMessage interface{}) {
- output := logger.Columnize(now.Format("2006/01/02 - 15:04:05"), latency, status, ip, method, path, message, headerMessage)
+ c.LogFunc = func(endTime time.Time, latency time.Duration, status, ip, method, path string, message interface{}, headerMessage interface{}) {
+ output := logger.Columnize(endTime.Format("2006/01/02 - 15:04:05"), latency, status, ip, method, path, message, headerMessage)
logFile.Write([]byte(output))
- }
+ } // or make use of the `LogFuncCtx`, see the '../request-logger-file-json' example for more.
// we don't want to use the logger
// to log requests to assets and etc
diff --git a/_examples/mvc/login/main.go b/_examples/mvc/login/main.go
index 3d77e08a..e1824b8f 100644
--- a/_examples/mvc/login/main.go
+++ b/_examples/mvc/login/main.go
@@ -28,7 +28,7 @@ func main() {
Reload(true)
app.RegisterView(tmpl)
- app.StaticWeb("/public", "./web/public")
+ app.HandleDir("/public", "./web/public")
app.OnAnyErrorCode(func(ctx iris.Context) {
ctx.ViewData("Message", ctx.Values().
diff --git a/_examples/routing/README.md b/_examples/routing/README.md
index 88c40db9..b776376e 100644
--- a/_examples/routing/README.md
+++ b/_examples/routing/README.md
@@ -559,89 +559,72 @@ Example Code:
package main
import (
- "net/http"
- "strings"
+ "net/http"
+ "strings"
- "github.com/kataras/iris"
+ "github.com/kataras/iris"
)
// In this example you'll just see one use case of .WrapRouter.
// You can use the .WrapRouter to add custom logic when or when not the router should
// be executed in order to execute the registered routes' handlers.
-//
-// To see how you can serve files on root "/" without a custom wrapper
-// just navigate to the "file-server/single-page-application" example.
-//
-// This is just for the proof of concept, you can skip this tutorial if it's too much for you.
+func newApp() *iris.Application {
+ app := iris.New()
+
+ app.OnErrorCode(iris.StatusNotFound, func(ctx iris.Context) {
+ ctx.HTML("Resource Not found")
+ })
+
+ app.Get("/profile/{username}", func(ctx iris.Context) {
+ ctx.Writef("Hello %s", ctx.Params().Get("username"))
+ })
+
+ app.HandleDir("/", "./public")
+
+ myOtherHandler := func(ctx iris.Context) {
+ ctx.Writef("inside a handler which is fired manually by our custom router wrapper")
+ }
+
+ // wrap the router with a native net/http handler.
+ // if url does not contain any "." (i.e: .css, .js...)
+ // (depends on the app , you may need to add more file-server exceptions),
+ // then the handler will execute the router that is responsible for the
+ // registered routes (look "/" and "/profile/{username}")
+ // if not then it will serve the files based on the root "/" path.
+ app.WrapRouter(func(w http.ResponseWriter, r *http.Request, router http.HandlerFunc) {
+ path := r.URL.Path
+
+ if strings.HasPrefix(path, "/other") {
+ // acquire and release a context in order to use it to execute
+ // our custom handler
+ // remember: we use net/http.Handler because here we are in the "low-level", before the router itself.
+ ctx := app.ContextPool.Acquire(w, r)
+ myOtherHandler(ctx)
+ app.ContextPool.Release(ctx)
+ return
+ }
+
+ router.ServeHTTP(w, r) // else continue serving routes as usual.
+ })
+
+ return app
+}
func main() {
- app := iris.New()
+ app := newApp()
- app.OnErrorCode(iris.StatusNotFound, func(ctx iris.Context) {
- ctx.HTML("Resource Not found")
- })
+ // http://localhost:8080
+ // http://localhost:8080/index.html
+ // http://localhost:8080/app.js
+ // http://localhost:8080/css/main.css
+ // http://localhost:8080/profile/anyusername
+ // http://localhost:8080/other/random
+ app.Run(iris.Addr(":8080"))
- app.Get("/", func(ctx iris.Context) {
- ctx.ServeFile("./public/index.html", false)
- })
-
- app.Get("/profile/{username}", func(ctx iris.Context) {
- ctx.Writef("Hello %s", ctx.Params().Get("username"))
- })
-
- // serve files from the root "/", if we used .StaticWeb it could override
- // all the routes because of the underline need of wildcard.
- // Here we will see how you can by-pass this behavior
- // by creating a new file server handler and
- // setting up a wrapper for the router(like a "low-level" middleware)
- // in order to manually check if we want to process with the router as normally
- // or execute the file server handler instead.
-
- // use of the .StaticHandler
- // which is the same as StaticWeb but it doesn't
- // registers the route, it just returns the handler.
- fileServer := app.StaticHandler("./public", false, false)
-
- // wrap the router with a native net/http handler.
- // if url does not contain any "." (i.e: .css, .js...)
- // (depends on the app , you may need to add more file-server exceptions),
- // then the handler will execute the router that is responsible for the
- // registered routes (look "/" and "/profile/{username}")
- // if not then it will serve the files based on the root "/" path.
- app.WrapRouter(func(w http.ResponseWriter, r *http.Request, router http.HandlerFunc) {
- path := r.URL.Path
- // Note that if path has suffix of "index.html" it will auto-permant redirect to the "/",
- // so our first handler will be executed instead.
-
- if !strings.Contains(path, ".") {
- // if it's not a resource then continue to the router as normally. <-- IMPORTANT
- router(w, r)
- return
- }
- // acquire and release a context in order to use it to execute
- // our file server
- // remember: we use net/http.Handler because here we are in the "low-level", before the router itself.
- ctx := app.ContextPool.Acquire(w, r)
- fileServer(ctx)
- app.ContextPool.Release(ctx)
- })
-
- // http://localhost:8080
- // http://localhost:8080/index.html
- // http://localhost:8080/app.js
- // http://localhost:8080/css/main.css
- // http://localhost:8080/profile/anyusername
- app.Run(iris.Addr(":8080"))
-
- // Note: In this example we just saw one use case,
- // you may want to .WrapRouter or .Downgrade in order to bypass the iris' default router, i.e:
- // you can use that method to setup custom proxies too.
- //
- // If you just want to serve static files on other path than root
- // you can just use the StaticWeb, i.e:
- // .StaticWeb("/static", "./public")
- // ________________________________requestPath, systemPath
+ // Note: In this example we just saw one use case,
+ // you may want to .WrapRouter or .Downgrade in order to bypass the iris' default router, i.e:
+ // you can use that method to setup custom proxies too.
}
```
@@ -1355,7 +1338,7 @@ type Context interface {
// You can define your own "Content-Type" with `context#ContentType`, before this function call.
//
// This function doesn't support resuming (by range),
- // use ctx.SendFile or router's `StaticWeb` instead.
+ // use ctx.SendFile or router's `HandleDir` instead.
ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error
// ServeFile serves a file (to send a file, a zip for example to the client you should use the `SendFile` instead)
// receives two parameters
@@ -1365,7 +1348,7 @@ type Context interface {
// You can define your own "Content-Type" with `context#ContentType`, before this function call.
//
// This function doesn't support resuming (by range),
- // use ctx.SendFile or router's `StaticWeb` instead.
+ // use ctx.SendFile or router's `HandleDir` instead.
//
// Use it when you want to serve dynamic files to the client.
ServeFile(filename string, gzipCompression bool) error
diff --git a/_examples/routing/custom-wrapper/main.go b/_examples/routing/custom-wrapper/main.go
index dfcd0402..ea8bfae4 100644
--- a/_examples/routing/custom-wrapper/main.go
+++ b/_examples/routing/custom-wrapper/main.go
@@ -10,11 +10,6 @@ import (
// In this example you'll just see one use case of .WrapRouter.
// You can use the .WrapRouter to add custom logic when or when not the router should
// be executed in order to execute the registered routes' handlers.
-//
-// To see how you can serve files on root "/" without a custom wrapper
-// just navigate to the "file-server/single-page-application" example.
-//
-// This is just for the proof of concept, you can skip this tutorial if it's too much for you.
func newApp() *iris.Application {
app := iris.New()
@@ -23,26 +18,15 @@ func newApp() *iris.Application {
ctx.HTML("Resource Not found")
})
- app.Get("/", func(ctx iris.Context) {
- ctx.ServeFile("./public/index.html", false)
- })
-
app.Get("/profile/{username}", func(ctx iris.Context) {
ctx.Writef("Hello %s", ctx.Params().Get("username"))
})
- // serve files from the root "/", if we used .StaticWeb it could override
- // all the routes because of the underline need of wildcard.
- // Here we will see how you can by-pass this behavior
- // by creating a new file server handler and
- // setting up a wrapper for the router(like a "low-level" middleware)
- // in order to manually check if we want to process with the router as normally
- // or execute the file server handler instead.
+ app.HandleDir("/", "./public")
- // use of the .StaticHandler
- // which is the same as StaticWeb but it doesn't
- // registers the route, it just returns the handler.
- fileServer := app.StaticHandler("./public", false, false)
+ myOtherHandler := func(ctx iris.Context) {
+ ctx.Writef("inside a handler which is fired manually by our custom router wrapper")
+ }
// wrap the router with a native net/http handler.
// if url does not contain any "." (i.e: .css, .js...)
@@ -52,19 +36,18 @@ func newApp() *iris.Application {
// if not then it will serve the files based on the root "/" path.
app.WrapRouter(func(w http.ResponseWriter, r *http.Request, router http.HandlerFunc) {
path := r.URL.Path
- // Note that if path has suffix of "index.html" it will auto-permant redirect to the "/",
- // so our first handler will be executed instead.
- if !strings.Contains(path, ".") { // if it's not a resource then continue to the router as normally.
- router(w, r)
+ if strings.HasPrefix(path, "/other") {
+ // acquire and release a context in order to use it to execute
+ // our custom handler
+ // remember: we use net/http.Handler because here we are in the "low-level", before the router itself.
+ ctx := app.ContextPool.Acquire(w, r)
+ myOtherHandler(ctx)
+ app.ContextPool.Release(ctx)
return
}
- // acquire and release a context in order to use it to execute
- // our file server
- // remember: we use net/http.Handler because here we are in the "low-level", before the router itself.
- ctx := app.ContextPool.Acquire(w, r)
- fileServer(ctx)
- app.ContextPool.Release(ctx)
+
+ router.ServeHTTP(w, r) // else continue serving routes as usual.
})
return app
@@ -78,14 +61,10 @@ func main() {
// http://localhost:8080/app.js
// http://localhost:8080/css/main.css
// http://localhost:8080/profile/anyusername
+ // http://localhost:8080/other/random
app.Run(iris.Addr(":8080"))
// Note: In this example we just saw one use case,
// you may want to .WrapRouter or .Downgrade in order to bypass the iris' default router, i.e:
// you can use that method to setup custom proxies too.
- //
- // If you just want to serve static files on other path than root
- // you can just use the StaticWeb, i.e:
- // .StaticWeb("/static", "./public")
- // ________________________________requestPath, systemPath
}
diff --git a/_examples/routing/custom-wrapper/main_test.go b/_examples/routing/custom-wrapper/main_test.go
index 74d36bdf..5e45a86d 100644
--- a/_examples/routing/custom-wrapper/main_test.go
+++ b/_examples/routing/custom-wrapper/main_test.go
@@ -56,4 +56,6 @@ func TestCustomWrapper(t *testing.T) {
Status(httptest.StatusOK).
Body().Equal(contents)
}
+
+ e.GET("/other/something").Expect().Status(httptest.StatusOK)
}
diff --git a/_examples/routing/custom-wrapper/public/index.html b/_examples/routing/custom-wrapper/public/index.html
index c52c1613..960869d7 100644
--- a/_examples/routing/custom-wrapper/public/index.html
+++ b/_examples/routing/custom-wrapper/public/index.html
@@ -1,7 +1,7 @@
- {{ .Page.Title }}
+ Index Page
diff --git a/_examples/routing/overview/main.go b/_examples/routing/overview/main.go
index d2ff7178..253daa98 100644
--- a/_examples/routing/overview/main.go
+++ b/_examples/routing/overview/main.go
@@ -35,17 +35,17 @@ func main() {
// maps to ./public/assets/css/bootstrap.min.css file at system location.
// GET: http://localhost:8080/assets/js/react.min.js
// maps to ./public/assets/js/react.min.js file at system location.
- app.StaticWeb("/assets", "./public/assets")
+ app.HandleDir("/assets", "./public/assets")
/* OR
// GET: http://localhost:8080/js/react.min.js
// maps to ./public/assets/js/react.min.js file at system location.
- app.StaticWeb("/js", "./public/assets/js")
+ app.HandleDir("/js", "./public/assets/js")
// GET: http://localhost:8080/css/bootstrap.min.css
// maps to ./public/assets/css/bootstrap.min.css file at system location.
- app.StaticWeb("/css", "./public/assets/css")
+ app.HandleDir("/css", "./public/assets/css")
*/
diff --git a/_examples/structuring/bootstrap/bootstrap/bootstrapper.go b/_examples/structuring/bootstrap/bootstrap/bootstrapper.go
index 9efa7df6..f8b94942 100644
--- a/_examples/structuring/bootstrap/bootstrap/bootstrapper.go
+++ b/_examples/structuring/bootstrap/bootstrap/bootstrapper.go
@@ -112,7 +112,7 @@ func (b *Bootstrapper) Bootstrap() *Bootstrapper {
// static files
b.Favicon(StaticAssets + Favicon)
- b.StaticWeb(StaticAssets[1:len(StaticAssets)-1], StaticAssets)
+ b.HandleDir(StaticAssets[1:len(StaticAssets)-1], StaticAssets)
// middleware, after static files
b.Use(recover.New())
diff --git a/_examples/structuring/login-mvc-single-responsibility-package/main.go b/_examples/structuring/login-mvc-single-responsibility-package/main.go
index 9f5e0911..e6232904 100644
--- a/_examples/structuring/login-mvc-single-responsibility-package/main.go
+++ b/_examples/structuring/login-mvc-single-responsibility-package/main.go
@@ -18,7 +18,7 @@ func main() {
app.RegisterView(iris.HTML("./views", ".html").Layout("shared/layout.html"))
- app.StaticWeb("/public", "./public")
+ app.HandleDir("/public", "./public")
mvc.Configure(app, configureMVC)
diff --git a/_examples/subdomains/multi/main.go b/_examples/subdomains/multi/main.go
index 5878f5f3..7c0627d9 100644
--- a/_examples/subdomains/multi/main.go
+++ b/_examples/subdomains/multi/main.go
@@ -11,8 +11,8 @@ func main() {
* Setup static files
*/
- app.StaticWeb("/assets", "./public/assets")
- app.StaticWeb("/upload_resources", "./public/upload_resources")
+ app.HandleDir("/assets", "./public/assets")
+ app.HandleDir("/upload_resources", "./public/upload_resources")
dashboard := app.Party("dashboard.")
{
diff --git a/_examples/subdomains/www/main_test.go b/_examples/subdomains/www/main_test.go
index db6c9421..ffd756cc 100644
--- a/_examples/subdomains/www/main_test.go
+++ b/_examples/subdomains/www/main_test.go
@@ -41,7 +41,7 @@ func TestSubdomainWWW(t *testing.T) {
}
host := "localhost:1111"
- e := httptest.New(t, app, httptest.URL("http://"+host), httptest.Debug(false))
+ e := httptest.New(t, app, httptest.Debug(false))
for _, test := range tests {
diff --git a/_examples/tutorial/dropzonejs/README.md b/_examples/tutorial/dropzonejs/README.md
index 9c8bbab6..b3380888 100644
--- a/_examples/tutorial/dropzonejs/README.md
+++ b/_examples/tutorial/dropzonejs/README.md
@@ -101,7 +101,7 @@ func main() {
app.RegisterView(iris.HTML("./views", ".html"))
// Make the /public route path to statically serve the ./public/... contents
- app.StaticWeb("/public", "./public")
+ app.HandleDir("/public", "./public")
// Render the actual form
// GET: http://localhost:8080
diff --git a/_examples/tutorial/dropzonejs/README_PART2.md b/_examples/tutorial/dropzonejs/README_PART2.md
index 331e0f19..536ea41e 100644
--- a/_examples/tutorial/dropzonejs/README_PART2.md
+++ b/_examples/tutorial/dropzonejs/README_PART2.md
@@ -168,7 +168,7 @@ func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
- app.StaticWeb("/public", "./public")
+ app.HandleDir("/public", "./public")
app.Get("/", func(ctx iris.Context) {
ctx.View("upload.html")
diff --git a/_examples/tutorial/dropzonejs/src/main.go b/_examples/tutorial/dropzonejs/src/main.go
index 86e54982..c6cc67ad 100644
--- a/_examples/tutorial/dropzonejs/src/main.go
+++ b/_examples/tutorial/dropzonejs/src/main.go
@@ -124,7 +124,7 @@ func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
- app.StaticWeb("/public", "./public")
+ app.HandleDir("/public", "./public")
app.Get("/", func(ctx iris.Context) {
ctx.View("upload.html")
diff --git a/_examples/tutorial/mongodb/README.md b/_examples/tutorial/mongodb/README.md
index 644a93d0..967eb118 100644
--- a/_examples/tutorial/mongodb/README.md
+++ b/_examples/tutorial/mongodb/README.md
@@ -8,7 +8,7 @@ Article is coming soon, follow and stay tuned
Read [the fully functional example](main.go).
```sh
-$ go get -u github.com/mongodb/mongo-go-driver
+$ go get -u go.mongodb.org/mongo-driver/...
$ go get -u github.com/joho/godotenv
```
diff --git a/_examples/tutorial/mongodb/main.go b/_examples/tutorial/mongodb/main.go
index 56702ebd..9ba3c191 100644
--- a/_examples/tutorial/mongodb/main.go
+++ b/_examples/tutorial/mongodb/main.go
@@ -1,6 +1,6 @@
package main
-// go get -u github.com/mongodb/mongo-go-driver
+// go get -u go.mongodb.org/mongo-driver
// go get -u github.com/joho/godotenv
import (
@@ -19,7 +19,7 @@ import (
"github.com/kataras/iris"
- "github.com/mongodb/mongo-go-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo"
)
const version = "0.0.1"
diff --git a/_examples/tutorial/mongodb/store/movie.go b/_examples/tutorial/mongodb/store/movie.go
index 3b995e47..133cddc9 100644
--- a/_examples/tutorial/mongodb/store/movie.go
+++ b/_examples/tutorial/mongodb/store/movie.go
@@ -4,11 +4,11 @@ import (
"context"
"errors"
- "github.com/mongodb/mongo-go-driver/bson"
- "github.com/mongodb/mongo-go-driver/bson/primitive"
- "github.com/mongodb/mongo-go-driver/mongo"
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+ "go.mongodb.org/mongo-driver/mongo"
// up to you:
- // "github.com/mongodb/mongo-go-driver/mongo/options"
+ // "go.mongodb.org/mongo-driver/mongo/options"
)
type Movie struct {
diff --git a/_examples/tutorial/online-visitors/main.go b/_examples/tutorial/online-visitors/main.go
index e6f3fa11..968ad284 100644
--- a/_examples/tutorial/online-visitors/main.go
+++ b/_examples/tutorial/online-visitors/main.go
@@ -23,7 +23,7 @@ func main() {
app.Any("/iris-ws.js", websocket.ClientHandler())
// register static assets request path and system directory
- app.StaticWeb("/js", "./static/assets/js")
+ app.HandleDir("/js", "./static/assets/js")
h := func(ctx iris.Context) {
ctx.ViewData("", page{PageID: "index page"})
diff --git a/_examples/tutorial/url-shortener/main.go b/_examples/tutorial/url-shortener/main.go
index 1771f766..7e9e488b 100644
--- a/_examples/tutorial/url-shortener/main.go
+++ b/_examples/tutorial/url-shortener/main.go
@@ -49,7 +49,7 @@ func newApp(db *DB) *iris.Application {
app.RegisterView(tmpl)
// Serve static files (css)
- app.StaticWeb("/static", "./resources")
+ app.HandleDir("/static", "./resources")
indexHandler := func(ctx iris.Context) {
ctx.ViewData("URL_COUNT", db.Len())
diff --git a/_examples/tutorial/vuejs-todo-mvc/README.md b/_examples/tutorial/vuejs-todo-mvc/README.md
index 9e0e582a..d46b7a80 100644
--- a/_examples/tutorial/vuejs-todo-mvc/README.md
+++ b/_examples/tutorial/vuejs-todo-mvc/README.md
@@ -500,7 +500,7 @@ func main() {
// no need for any server-side template here,
// actually if you're going to just use vue without any
// back-end services, you can just stop afer this line and start the server.
- app.StaticWeb("/", "./public")
+ app.HandleDir("/", "./public")
// configure the http sessions.
sess := sessions.New(sessions.Config{
diff --git a/_examples/tutorial/vuejs-todo-mvc/src/web/main.go b/_examples/tutorial/vuejs-todo-mvc/src/web/main.go
index 34e5ed88..d1c3815b 100644
--- a/_examples/tutorial/vuejs-todo-mvc/src/web/main.go
+++ b/_examples/tutorial/vuejs-todo-mvc/src/web/main.go
@@ -19,7 +19,7 @@ func main() {
// no need for any server-side template here,
// actually if you're going to just use vue without any
// back-end services, you can just stop afer this line and start the server.
- app.StaticWeb("/", "./public")
+ app.HandleDir("/", "./public")
// configure the http sessions.
sess := sessions.New(sessions.Config{
diff --git a/_examples/view/template_django_0/main.go b/_examples/view/template_django_0/main.go
new file mode 100644
index 00000000..f1bc8957
--- /dev/null
+++ b/_examples/view/template_django_0/main.go
@@ -0,0 +1,44 @@
+package main
+
+import (
+ "time"
+
+ "github.com/kataras/iris"
+
+ // optionally, registers filters like `timesince`.
+ _ "github.com/flosch/pongo2-addons"
+)
+
+var startTime = time.Now()
+
+func main() {
+ app := iris.New()
+
+ tmpl := iris.Django("./templates", ".html")
+ tmpl.Reload(true) // reload templates on each request (development mode)
+ tmpl.AddFunc("greet", func(s string) string { // {{greet(name)}}
+ return "Greetings " + s + "!"
+ })
+
+ // tmpl.RegisterFilter("myFilter", myFilter) // {{"simple input for filter"|myFilter}}
+ app.RegisterView(tmpl)
+
+ app.Get("/", hi)
+
+ // http://localhost:8080
+ app.Run(iris.Addr(":8080"), iris.WithCharset("UTF-8")) // defaults to that but you can change it.
+}
+
+func hi(ctx iris.Context) {
+ // ctx.ViewData("title", "Hi Page")
+ // ctx.ViewData("name", "iris")
+ // ctx.ViewData("serverStartTime", startTime)
+ // or if you set all view data in the same handler you can use the
+ // iris.Map/pongo2.Context/map[string]interface{}, look below:
+
+ ctx.View("hi.html", iris.Map{
+ "title": "Hi Page",
+ "name": "iris",
+ "serverStartTime": startTime,
+ })
+}
diff --git a/_examples/view/template_django_0/templates/hi.html b/_examples/view/template_django_0/templates/hi.html
new file mode 100644
index 00000000..945782e4
--- /dev/null
+++ b/_examples/view/template_django_0/templates/hi.html
@@ -0,0 +1,12 @@
+
+
+{{title}}
+
+
+ Hi {{name|capfirst}}
+
+ {{greet(name)}}
+
+ Server started about {{serverStartTime|timesince}}. Refresh the page to see different result
+
+
diff --git a/_examples/webassembly/basic/main.go b/_examples/webassembly/basic/main.go
index 4099da53..9bf19515 100644
--- a/_examples/webassembly/basic/main.go
+++ b/_examples/webassembly/basic/main.go
@@ -14,7 +14,7 @@ func main() {
// we could serve your assets like this the shake of the example,
// never include the .go files there in production.
- app.StaticWeb("/", "./client")
+ app.HandleDir("/", "./client")
app.Get("/", func(ctx iris.Context) {
ctx.ServeFile("./client/hello.html", false) // true for gzip.
diff --git a/_examples/websocket/basic/server.go b/_examples/websocket/basic/server.go
index be101e45..e030fe9b 100644
--- a/_examples/websocket/basic/server.go
+++ b/_examples/websocket/basic/server.go
@@ -52,7 +52,7 @@ func main() {
})
// serves the npm browser websocket client usage example.
- app.StaticWeb("/browserify", "./browserify")
+ app.HandleDir("/browserify", "./browserify")
app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))
}
diff --git a/_examples/websocket/native-messages/main.go b/_examples/websocket/native-messages/main.go
index 33de1d56..900b08cb 100644
--- a/_examples/websocket/native-messages/main.go
+++ b/_examples/websocket/native-messages/main.go
@@ -35,7 +35,7 @@ func main() {
// see the inline javascript code i the websockets.html, this endpoint is used to connect to the server.
app.Get("/my_endpoint", ws.Handler())
- app.StaticWeb("/js", "./static/js") // serve our custom javascript code
+ app.HandleDir("/js", "./static/js") // serve our custom javascript code
app.Get("/", func(ctx iris.Context) {
ctx.ViewData("", clientPage{"Client Page", "localhost:8080"})
diff --git a/_examples/websocket/secure/main.go b/_examples/websocket/secure/main.go
index 5bedbb5e..ce41e87c 100644
--- a/_examples/websocket/secure/main.go
+++ b/_examples/websocket/secure/main.go
@@ -32,7 +32,7 @@ func main() {
ctx.Write(websocket.ClientSource)
})
- app.StaticWeb("/js", "./static/js")
+ app.HandleDir("/js", "./static/js")
app.Get("/", func(ctx iris.Context) {
// send our custom javascript source file before client really asks for that
// using the go v1.8's HTTP/2 Push.
diff --git a/_examples/websocket/third-party-socketio/main.go b/_examples/websocket/third-party-socketio/main.go
index c2a762e7..ae20cc27 100644
--- a/_examples/websocket/third-party-socketio/main.go
+++ b/_examples/websocket/third-party-socketio/main.go
@@ -38,7 +38,7 @@ func main() {
// serve the index.html and the javascript libraries at
// http://localhost:8080
- app.StaticWeb("/", "./public")
+ app.HandleDir("/", "./public")
app.Run(iris.Addr("localhost:8080"), iris.WithoutPathCorrection)
}
diff --git a/cache/browser.go b/cache/browser.go
index c03f21f5..5cb1186b 100644
--- a/cache/browser.go
+++ b/cache/browser.go
@@ -92,7 +92,7 @@ const ifNoneMatchHeaderKey = "If-None-Match"
//
// Usage with combination of `StaticCache`:
// assets := app.Party("/assets", cache.StaticCache(24 * time.Hour), ETag)
-// assets.StaticWeb("/", "./assets") or StaticEmbedded("/", "./assets") or StaticEmbeddedGzip("/", "./assets").
+// assets.HandleDir("/", "./assets")
//
// Similar to `Cache304` but it doesn't depends on any "modified date", it uses just the ETag and If-None-Match headers.
//
@@ -124,7 +124,7 @@ var ETag = func(ctx context.Context) {
// by watching system directories changes manually and use of the `ctx.WriteWithExpiration`
// with a "modtime" based on the file modified date,
// can be used on Party's that contains a static handler,
-// i.e `StaticWeb`, `StaticEmbedded` or even `StaticEmbeddedGzip`.
+// i.e `HandleDir`.
var Cache304 = func(expiresEvery time.Duration) context.Handler {
return func(ctx context.Context) {
now := time.Now()
diff --git a/cache/client/handler.go b/cache/client/handler.go
index d0a7b893..0120241c 100644
--- a/cache/client/handler.go
+++ b/cache/client/handler.go
@@ -71,8 +71,6 @@ func parseLifeChanger(ctx context.Context) entry.LifeChanger {
}
}
-///TODO: debug this and re-run the parallel tests on larger scale,
-// because I think we have a bug here when `core/router#StaticWeb` is used after this middleware.
func (h *Handler) ServeHTTP(ctx context.Context) {
// check for pre-cache validators, if at least one of them return false
// for this specific request, then skip the whole cache
diff --git a/context/context.go b/context/context.go
index 898fbf79..56f96302 100644
--- a/context/context.go
+++ b/context/context.go
@@ -209,6 +209,12 @@ type Context interface {
Proceed(Handler) bool
// HandlerName returns the current handler's name, helpful for debugging.
HandlerName() string
+ // HandlerFileLine returns the current running handler's function source file and line information.
+ // Useful mostly when debugging.
+ HandlerFileLine() (file string, line int)
+ // RouteName returns the route name that this handler is running on.
+ // Note that it will return empty on not found handlers.
+ RouteName() string
// Next calls all the next handler from the handlers chain,
// it should be used inside a middleware.
//
@@ -626,7 +632,7 @@ type Context interface {
// 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.)
+ // (e.g with WriteWithExpiration or HandleDir 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
@@ -775,7 +781,7 @@ type Context interface {
// You can define your own "Content-Type" with `context#ContentType`, before this function call.
//
// This function doesn't support resuming (by range),
- // use ctx.SendFile or router's `StaticWeb` instead.
+ // use ctx.SendFile or router's `HandleDir` instead.
ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error
// ServeFile serves a file (to send a file, a zip for example to the client you should use the `SendFile` instead)
// receives two parameters
@@ -785,7 +791,7 @@ type Context interface {
// You can define your own "Content-Type" with `context#ContentType`, before this function call.
//
// This function doesn't support resuming (by range),
- // use ctx.SendFile or router's `StaticWeb` instead.
+ // use ctx.SendFile or router's `HandleDir` instead.
//
// Use it when you want to serve dynamic files to the client.
ServeFile(filename string, gzipCompression bool) error
@@ -1208,12 +1214,21 @@ func (ctx *context) Proceed(h Handler) bool {
// HandlerName returns the current handler's name, helpful for debugging.
func (ctx *context) HandlerName() string {
- if name := ctx.currentRouteName; name != "" {
- return name
- }
return HandlerName(ctx.handlers[ctx.currentHandlerIndex])
}
+// HandlerFileLine returns the current running handler's function source file and line information.
+// Useful mostly when debugging.
+func (ctx *context) HandlerFileLine() (file string, line int) {
+ return HandlerFileLine(ctx.handlers[ctx.currentHandlerIndex])
+}
+
+// RouteName returns the route name that this handler is running on.
+// Note that it will return empty on not found handlers.
+func (ctx *context) RouteName() string {
+ return ctx.currentRouteName
+}
+
// Next is the function that executed when `ctx.Next()` is called.
// It can be changed to a customized one if needed (very advanced usage).
//
@@ -2476,7 +2491,7 @@ func (ctx *context) SetLastModified(modtime time.Time) {
// 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.)
+// (e.g with WriteWithExpiration or HandleDir 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
@@ -3122,7 +3137,10 @@ func (ctx *context) ServeContent(content io.ReadSeeker, filename string, modtime
return nil
}
- ctx.ContentType(filename)
+ if ctx.GetContentType() == "" {
+ ctx.ContentType(filename)
+ }
+
ctx.SetLastModified(modtime)
var out io.Writer
if gzipCompression && ctx.ClientSupportsGzip() {
@@ -3150,7 +3168,7 @@ func (ctx *context) ServeContent(content io.ReadSeeker, filename string, modtime
func (ctx *context) ServeFile(filename string, gzipCompression bool) error {
f, err := os.Open(filename)
if err != nil {
- return fmt.Errorf("%d", 404)
+ return fmt.Errorf("%d", http.StatusNotFound)
}
defer f.Close()
fi, _ := f.Stat()
diff --git a/context/handler.go b/context/handler.go
index a78423b9..7ae28914 100644
--- a/context/handler.go
+++ b/context/handler.go
@@ -3,6 +3,7 @@ package context
import (
"reflect"
"runtime"
+ "strings"
)
// A Handler responds to an HTTP request.
@@ -26,15 +27,35 @@ type Handler func(Context)
// See `Handler` for more.
type Handlers []Handler
-// HandlerName returns the name, the handler function informations.
-// Same as `context.HandlerName`.
+// HandlerName returns the handler's function name.
+// See `context.HandlerName` to get function name of the current running handler in the chain.
func HandlerName(h Handler) string {
pc := reflect.ValueOf(h).Pointer()
- // l, n := runtime.FuncForPC(pc).FileLine(pc)
- // return fmt.Sprintf("%s:%d", l, n)
return runtime.FuncForPC(pc).Name()
}
+// HandlerFileLine returns the handler's file and line information.
+// See `context.HandlerFileLine` to get the file, line of the current running handler in the chain.
+func HandlerFileLine(h Handler) (file string, line int) {
+ pc := reflect.ValueOf(h).Pointer()
+ return runtime.FuncForPC(pc).FileLine(pc)
+}
+
+// MainHandlerName tries to find the main handler than end-developer
+// registered on the provided chain of handlers and returns its function name.
+func MainHandlerName(handlers Handlers) (name string) {
+ for i := 0; i < len(handlers); i++ {
+ name = HandlerName(handlers[i])
+ if !strings.HasPrefix(name, "github.com/kataras/iris") ||
+ strings.HasPrefix(name, "github.com/kataras/iris/core/router.StripPrefix") ||
+ strings.HasPrefix(name, "github.com/kataras/iris/core/router.FileServer") {
+ break
+ }
+ }
+
+ return
+}
+
// Filter is just a type of func(Handler) bool which reports whether an action must be performed
// based on the incoming request.
//
diff --git a/context/response_writer.go b/context/response_writer.go
index 419be652..9cd05f20 100644
--- a/context/response_writer.go
+++ b/context/response_writer.go
@@ -60,7 +60,7 @@ type ResponseWriter interface {
// Written should returns the total length of bytes that were being written to the client.
// In addition iris provides some variables to help low-level actions:
// NoWritten, means that nothing were written yet and the response writer is still live.
- // StatusCodeWritten, means that status code were written but no other bytes are written to the client, response writer may closed.
+ // StatusCodeWritten, means that status code was written but no other bytes are written to the client, response writer may closed.
// > 0 means that the reply was written and it's the total number of bytes were written.
Written() int
diff --git a/context/route.go b/context/route.go
index 7c680e7d..7db074a9 100644
--- a/context/route.go
+++ b/context/route.go
@@ -1,6 +1,13 @@
package context
-import "github.com/kataras/iris/macro"
+import (
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/kataras/iris/macro"
+)
// RouteReadOnly allows decoupled access to the current route
// inside the context.
@@ -42,4 +49,59 @@ type RouteReadOnly interface {
// MainHandlerName returns the first registered handler for the route.
MainHandlerName() string
+
+ // StaticSites if not empty, refers to the system (or virtual if embedded) directory
+ // and sub directories that this "GET" route was registered to serve files and folders
+ // that contain index.html (a site). The index handler may registered by other
+ // route, manually or automatic by the framework,
+ // get the route by `Application#GetRouteByPath(staticSite.RequestPath)`.
+ StaticSites() []StaticSite
+}
+
+// StaticSite is a structure which is used as field on the `Route`
+// and route registration on the `APIBuilder#HandleDir`.
+// See `GetStaticSites` and `APIBuilder#HandleDir`.
+type StaticSite struct {
+ Dir string `json:"dir"`
+ RequestPath string `json:"requestPath"`
+}
+
+// GetStaticSites search for a relative filename of "indexName" in "rootDir" and all its subdirectories
+// and returns a list of structures which contains the directory found an "indexName" and the request path
+// that a route should be registered to handle this "indexName".
+// The request path is given by the directory which an index exists on.
+func GetStaticSites(rootDir, rootRequestPath, indexName string) (sites []StaticSite) {
+ f, err := os.Open(rootDir)
+ if err != nil {
+ return nil
+ }
+
+ list, err := f.Readdir(-1)
+ f.Close()
+ if err != nil {
+ return nil
+ }
+
+ if len(list) == 0 {
+ return nil
+ }
+
+ for _, l := range list {
+ dir := filepath.Join(rootDir, l.Name())
+
+ if l.IsDir() {
+ sites = append(sites, GetStaticSites(dir, path.Join(rootRequestPath, l.Name()), indexName)...)
+ continue
+ }
+
+ if l.Name() == strings.TrimPrefix(indexName, "/") {
+ sites = append(sites, StaticSite{
+ Dir: filepath.FromSlash(rootDir),
+ RequestPath: rootRequestPath,
+ })
+ continue
+ }
+ }
+
+ return
}
diff --git a/core/errors/reporter.go b/core/errors/reporter.go
index 3a4ebf86..b38111f7 100644
--- a/core/errors/reporter.go
+++ b/core/errors/reporter.go
@@ -64,7 +64,7 @@ func (r *Reporter) AddErr(err error) bool {
}
if stackErr, ok := err.(StackError); ok {
- r.addStack(stackErr.Stack())
+ r.addStack("", stackErr.Stack())
} else {
r.mu.Lock()
r.wrapper = r.wrapper.AppendErr(err)
@@ -108,7 +108,7 @@ func (r *Reporter) Describe(format string, err error) {
return
}
if stackErr, ok := err.(StackError); ok {
- r.addStack(stackErr.Stack())
+ r.addStack(format, stackErr.Stack())
return
}
@@ -126,12 +126,15 @@ func (r *Reporter) Stack() []Error {
return r.wrapper.Stack
}
-func (r *Reporter) addStack(stack []Error) {
+func (r *Reporter) addStack(format string, stack []Error) {
for _, e := range stack {
if e.Error() == "" {
continue
}
r.mu.Lock()
+ if format != "" {
+ e = New(format).Format(e)
+ }
r.wrapper = r.wrapper.AppendErr(e)
r.mu.Unlock()
}
diff --git a/core/router/api_builder.go b/core/router/api_builder.go
index 310c89d5..13b61ec6 100644
--- a/core/router/api_builder.go
+++ b/core/router/api_builder.go
@@ -21,15 +21,15 @@ var (
// "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD",
// "PATCH", "OPTIONS", "TRACE".
AllMethods = []string{
- "GET",
- "POST",
- "PUT",
- "DELETE",
- "CONNECT",
- "HEAD",
- "PATCH",
- "OPTIONS",
- "TRACE",
+ http.MethodGet,
+ http.MethodPost,
+ http.MethodPut,
+ http.MethodDelete,
+ http.MethodConnect,
+ http.MethodHead,
+ http.MethodPatch,
+ http.MethodOptions,
+ http.MethodTrace,
}
)
@@ -37,20 +37,71 @@ var (
// all the routes.
type repository struct {
routes []*Route
+ pos map[string]int
}
-func (r *repository) register(route *Route) {
- for _, r := range r.routes {
- if r.String() == route.String() {
- return // do not register any duplicates, the sooner the better.
+func (repo *repository) remove(route *Route) bool {
+ for i, r := range repo.routes {
+ if r == route {
+ return repo.removeByIndex(i)
}
}
- r.routes = append(r.routes, route)
+ return false
}
-func (r *repository) get(routeName string) *Route {
- for _, r := range r.routes {
+func (repo *repository) removeByPath(tmplPath string) bool {
+ if repo.pos != nil {
+ if idx, ok := repo.pos[tmplPath]; ok {
+ return repo.removeByIndex(idx)
+ }
+ }
+
+ return false
+}
+
+func (repo *repository) removeByName(routeName string) bool {
+ for i, r := range repo.routes {
+ if r.Name == routeName {
+ return repo.removeByIndex(i)
+ }
+ }
+
+ return false
+}
+
+func (repo *repository) removeByIndex(idx int) bool {
+ n := len(repo.routes)
+
+ if n == 0 {
+ return false
+ }
+
+ if idx >= n {
+ return false
+ }
+
+ if n == 1 && idx == 0 {
+ repo.routes = repo.routes[0:0]
+ repo.pos = nil
+ return true
+ }
+
+ r := repo.routes[idx]
+ if r == nil {
+ return false
+ }
+
+ repo.routes = append(repo.routes[:idx], repo.routes[idx+1:]...)
+ if repo.pos != nil {
+ delete(repo.pos, r.Path)
+ }
+
+ return true
+}
+
+func (repo *repository) get(routeName string) *Route {
+ for _, r := range repo.routes {
if r.Name == routeName {
return r
}
@@ -58,8 +109,37 @@ func (r *repository) get(routeName string) *Route {
return nil
}
-func (r *repository) getAll() []*Route {
- return r.routes
+func (repo *repository) getByPath(tmplPath string) *Route {
+ if repo.pos != nil {
+ if idx, ok := repo.pos[tmplPath]; ok {
+ if len(repo.routes) > idx {
+ return repo.routes[idx]
+ }
+ }
+ }
+
+ return nil
+}
+
+func (repo *repository) getAll() []*Route {
+ return repo.routes
+}
+
+func (repo *repository) register(route *Route) {
+ for i, r := range repo.routes {
+ if route.Equal(r) {
+ // replace existing with the latest one.
+ repo.routes = append(repo.routes[:i], repo.routes[i+1:]...)
+ continue
+ }
+ }
+
+ repo.routes = append(repo.routes, route)
+ if repo.pos == nil {
+ repo.pos = make(map[string]int)
+ }
+
+ repo.pos[route.tmpl.Src] = len(repo.routes) - 1
}
// APIBuilder the visible API for constructing the router
@@ -181,17 +261,13 @@ func (api *APIBuilder) SetExecutionRules(executionRules ExecutionRules) Party {
return api
}
-// Handle registers a route to the server's api.
-// if empty method is passed then handler(s) are being registered to all methods, same as .Any.
-//
-// Returns a *Route, app will throw any errors later on.
-func (api *APIBuilder) Handle(method string, relativePath string, handlers ...context.Handler) *Route {
+func (api *APIBuilder) createRoutes(methods []string, relativePath string, handlers ...context.Handler) []*Route {
// if relativePath[0] != '/' {
// return nil, errors.New("path should start with slash and should not be empty")
// }
- if method == "" || method == "ALL" || method == "ANY" { // then use like it was .Any
- return api.Any(relativePath, handlers...)[0]
+ if len(methods) == 0 || methods[0] == "ALL" || methods[0] == "ANY" { // then use like it was .Any
+ return api.Any(relativePath, handlers...)
}
// no clean path yet because of subdomain indicator/separator which contains a dot.
@@ -206,7 +282,7 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
fullpath := api.relativePath + relativePath // for now, keep the last "/" if any, "/xyz/"
if len(handlers) == 0 {
- api.reporter.Add("missing handlers for route %s: %s", method, fullpath)
+ api.reporter.Add("missing handlers for route %s: %s", strings.Join(methods, ", "), fullpath)
return nil
}
@@ -222,7 +298,8 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
mainHandlers := context.Handlers(handlers)
// before join the middleware + handlers + done handlers and apply the execution rules.
- possibleMainHandlerName := context.HandlerName(mainHandlers[0])
+
+ possibleMainHandlerName := context.MainHandlerName(mainHandlers)
// TODO: for UseGlobal/DoneGlobal that doesn't work.
applyExecutionRules(api.handlerExecutionRules, &beginHandlers, &doneHandlers, &mainHandlers)
@@ -237,24 +314,36 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
subdomain, path := splitSubdomainAndPath(fullpath)
// if allowMethods are empty, then simply register with the passed, main, method.
- methods := append(api.allowMethods, method)
+ methods = append(api.allowMethods, methods...)
- var (
- route *Route // the latest one is this route registered, see methods append.
- err error // not used outside of loop scope.
- )
+ routes := make([]*Route, len(methods), len(methods))
- for _, m := range methods {
- route, err = NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, *api.macros)
+ for i, m := range methods {
+ route, err := NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, *api.macros)
if err != nil { // template path parser errors:
- api.reporter.Add("%v -> %s:%s:%s", err, method, subdomain, path)
- return nil // fail on first error.
+ api.reporter.Add("%v -> %s:%s:%s", err, m, subdomain, path)
+ continue
}
// Add UseGlobal & DoneGlobal Handlers
- route.use(api.beginGlobalHandlers)
- route.done(api.doneGlobalHandlers)
+ route.Use(api.beginGlobalHandlers...)
+ route.Done(api.doneGlobalHandlers...)
+ routes[i] = route
+ }
+
+ return routes
+}
+
+// Handle registers a route to the server's api.
+// if empty method is passed then handler(s) are being registered to all methods, same as .Any.
+//
+// Returns a *Route, app will throw any errors later on.
+func (api *APIBuilder) Handle(method string, relativePath string, handlers ...context.Handler) *Route {
+ routes := api.createRoutes([]string{method}, relativePath, handlers...)
+
+ var route *Route // the last one is returned.
+ for _, route = range routes {
// global
api.routes.register(route)
}
@@ -301,6 +390,61 @@ func (api *APIBuilder) HandleMany(methodOrMulti string, relativePathorMulti stri
return
}
+// HandleDir registers a handler that serves HTTP requests
+// with the contents of a file system (physical or embedded).
+//
+// first parameter : the route path
+// second parameter : the system or the embedded directory that needs to be served
+// third parameter : not required, the directory options, set fields is optional.
+//
+// for more options look router.FileServer.
+//
+// api.HandleDir("/static", "./assets", DirOptions {ShowList: true, Gzip: true, IndexName: "index.html"})
+//
+// Returns the GET *Route.
+//
+// Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server
+func (api *APIBuilder) HandleDir(requestPath, directory string, opts ...DirOptions) (getRoute *Route) {
+ options := getDirOptions(opts...)
+
+ h := FileServer(directory, options)
+
+ // if subdomain, we get the full path of the path only,
+ // because a subdomain can have parties as well
+ // and we need that path to call the `StripPrefix`.
+ if _, fullpath := splitSubdomainAndPath(joinPath(api.relativePath, requestPath)); fullpath != "/" {
+ h = StripPrefix(fullpath, h)
+
+ }
+
+ requestPath = joinPath(requestPath, WildcardFileParam())
+ routes := api.createRoutes([]string{http.MethodGet, http.MethodHead}, requestPath, h)
+ getRoute = routes[0]
+ // we get all index, including sub directories even if those
+ // are already managed by the static handler itself.
+ staticSites := context.GetStaticSites(directory, getRoute.StaticPath(), options.IndexName)
+ for _, s := range staticSites {
+ // if the end-dev did manage that index route manually already
+ // then skip the auto-registration.
+ //
+ // Also keep note that end-dev is still able to replace this route and manage by him/herself
+ // later on by a simple `Handle/Get/` call, refer to `repository#register`.
+ if api.GetRouteByPath(s.RequestPath) != nil {
+ continue
+ }
+
+ routes = append(routes, api.createRoutes([]string{http.MethodGet}, s.RequestPath, h)...)
+ getRoute.StaticSites = append(getRoute.StaticSites, s)
+ }
+
+ for _, route := range routes {
+ route.MainHandlerName = `HandleDir(directory: "` + directory + `")`
+ api.routes.register(route)
+ }
+
+ return getRoute
+}
+
// Party groups routes which may have the same prefix and share same handlers,
// returns that new rich subrouter.
//
@@ -432,19 +576,9 @@ func (api *APIBuilder) GetRoute(routeName string) *Route {
return api.routes.get(routeName)
}
-// GetRouteReadOnly returns the registered "read-only" route based on its name, otherwise nil.
-// One note: "routeName" should be case-sensitive. Used by the context to get the current route.
-// It returns an interface instead to reduce wrong usage and to keep the decoupled design between
-// the context and the routes.
-// Look `GetRoutesReadOnly` to fetch a list of all registered routes.
-//
-// Look `GetRoute` for more.
-func (api *APIBuilder) GetRouteReadOnly(routeName string) context.RouteReadOnly {
- r := api.GetRoute(routeName)
- if r == nil {
- return nil
- }
- return routeReadOnlyWrapper{r}
+// GetRouteByPath returns the registered route based on the template path (`Route.Tmpl().Src`).
+func (api *APIBuilder) GetRouteByPath(tmplPath string) *Route {
+ return api.routes.getByPath(tmplPath)
}
// GetRoutesReadOnly returns the registered routes with "read-only" access,
@@ -465,6 +599,31 @@ func (api *APIBuilder) GetRoutesReadOnly() []context.RouteReadOnly {
return readOnlyRoutes
}
+// GetRouteReadOnly returns the registered "read-only" route based on its name, otherwise nil.
+// One note: "routeName" should be case-sensitive. Used by the context to get the current route.
+// It returns an interface instead to reduce wrong usage and to keep the decoupled design between
+// the context and the routes.
+// Look `GetRoutesReadOnly` to fetch a list of all registered routes.
+//
+// Look `GetRoute` for more.
+func (api *APIBuilder) GetRouteReadOnly(routeName string) context.RouteReadOnly {
+ r := api.GetRoute(routeName)
+ if r == nil {
+ return nil
+ }
+ return routeReadOnlyWrapper{r}
+}
+
+// GetRouteReadOnlyByPath returns the registered read-only route based on the template path (`Route.Tmpl().Src`).
+func (api *APIBuilder) GetRouteReadOnlyByPath(tmplPath string) context.RouteReadOnly {
+ r := api.GetRouteByPath(tmplPath)
+ if r == nil {
+ return nil
+ }
+
+ return routeReadOnlyWrapper{r}
+}
+
// Use appends Handler(s) to the current Party's routes and child routes.
// If the current Party is the root, then it registers the middleware to all child Parties' routes too.
//
@@ -488,7 +647,7 @@ func (api *APIBuilder) Use(handlers ...context.Handler) {
// It's always a good practise to call it right before the `Application#Run` function.
func (api *APIBuilder) UseGlobal(handlers ...context.Handler) {
for _, r := range api.routes.routes {
- r.use(handlers) // prepend the handlers to the existing routes
+ r.Use(handlers...) // prepend the handlers to the existing routes
}
// set as begin handlers for the next routes as well.
api.beginGlobalHandlers = append(api.beginGlobalHandlers, handlers...)
@@ -514,7 +673,7 @@ func (api *APIBuilder) Done(handlers ...context.Handler) {
// It's always a good practise to call it right before the `Application#Run` function.
func (api *APIBuilder) DoneGlobal(handlers ...context.Handler) {
for _, r := range api.routes.routes {
- r.done(handlers) // append the handlers to the existing routes
+ r.Done(handlers...) // append the handlers to the existing routes
}
// set as done handlers for the next routes as well.
api.doneGlobalHandlers = append(api.doneGlobalHandlers, handlers...)
@@ -616,51 +775,9 @@ func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) (ro
return
}
-func (api *APIBuilder) registerResourceRoute(target, reqPath string, h context.Handler) *Route {
- head := api.Head(reqPath, h)
- head.StaticTarget = target
- get := api.Get(reqPath, h)
- get.StaticTarget = target
- return get
-}
-
-// StaticHandler returns a new Handler which is ready
-// to serve all kind of static files.
-//
-// Note:
-// The only difference from package-level `StaticHandler`
-// is that this `StaticHandler`` receives a request path which
-// is appended to the party's relative path and stripped here.
-//
-// Usage:
-// app := iris.New()
-// ...
-// mySubdomainFsServer := app.Party("mysubdomain.")
-// h := mySubdomainFsServer.StaticHandler("./static_files", false, false)
-// /* http://mysubdomain.mydomain.com/static/css/style.css */
-// mySubdomainFsServer.Get("/static", h)
-// ...
-//
-func (api *APIBuilder) StaticHandler(systemPath string, showList bool, gzip bool) context.Handler {
- // Note: this doesn't need to be here but we'll keep it for consistently
- return StaticHandler(systemPath, showList, gzip)
-}
-
-// StaticServe serves a directory as web resource.
-// Same as `StaticWeb`.
-// DEPRECATED; use `StaticWeb` or `StaticHandler` (for more options) instead.
-func (api *APIBuilder) StaticServe(systemPath string, requestPath ...string) *Route {
- var reqPath string
-
- if len(requestPath) == 0 {
- reqPath = strings.Replace(systemPath, string(os.PathSeparator), "/", -1) // replaces any \ to /
- reqPath = strings.Replace(reqPath, "//", "/", -1) // for any case, replaces // to /
- reqPath = strings.Replace(reqPath, ".", "", -1) // replace any dots (./mypath -> /mypath)
- } else {
- reqPath = requestPath[0]
- }
-
- return api.StaticWeb(reqPath, systemPath)
+func (api *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) *Route {
+ api.Head(reqPath, h)
+ return api.Get(reqPath, h)
}
// StaticContent registers a GET and HEAD method routes to the requestPath
@@ -677,58 +794,7 @@ func (api *APIBuilder) StaticContent(reqPath string, cType string, content []byt
}
}
- return api.registerResourceRoute(StaticContentTarget, reqPath, h)
-}
-
-// StaticEmbedded used when files are distributed inside the app executable, using go-bindata mostly
-// First parameter is the request path, the path which the files in the vdir will be served to, for example "/static"
-// Second parameter is the (virtual) directory path, for example "./assets" (no trailing slash),
-// Third parameter is the Asset function
-// Forth parameter is the AssetNames function.
-//
-// Returns the GET *Route.
-//
-// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-files-into-app
-func (api *APIBuilder) StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route {
- return api.staticEmbedded(requestPath, vdir, assetFn, namesFn, false)
-}
-
-// StaticEmbeddedGzip registers a route which can serve embedded gziped files
-// that are embedded using the https://github.com/kataras/bindata tool and only.
-// It's 8 times faster than the `StaticEmbeddedHandler` with `go-bindata` but
-// it sends gzip response only, so the client must be aware that is expecting a gzip body
-// (browsers and most modern browsers do that, so you can use it without fair).
-//
-// First parameter is the request path, the path which the files in the vdir will be served to, for example "/static"
-// Second parameter is the (virtual) directory path, for example "./assets" (no trailing slash),
-// Third parameter is the GzipAsset function
-// Forth parameter is the GzipAssetNames function.
-//
-// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-gziped-files-into-app
-func (api *APIBuilder) StaticEmbeddedGzip(requestPath string, vdir string, gzipAssetFn func(name string) ([]byte, error), gzipNamesFn func() []string) *Route {
- return api.staticEmbedded(requestPath, vdir, gzipAssetFn, gzipNamesFn, true)
-}
-
-// look fs.go#StaticEmbeddedHandler
-func (api *APIBuilder) staticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string, assetsGziped bool) *Route {
- fullpath := joinPath(api.relativePath, requestPath)
- // if subdomain,
- // here we get the full path of the path only,
- // because a subdomain can have parties as well
- // and we need that path to call the `StripPrefix`.
- _, fullpath = splitSubdomainAndPath(fullpath)
-
- paramName := "file"
- requestPath = joinPath(requestPath, WildcardParam(paramName))
-
- h := StaticEmbeddedHandler(vdir, assetFn, namesFn, assetsGziped)
-
- if fullpath != "/" {
- h = StripPrefix(fullpath, h)
- }
-
- // it handles the subdomain(root Party) of this party as well, if any.
- return api.registerResourceRoute(vdir, requestPath, h)
+ return api.registerResourceRoute(reqPath, h)
}
// errDirectoryFileNotFound returns an error with message: 'Directory or file %s couldn't found. Trace: +error trace'
@@ -787,48 +853,7 @@ func (api *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
reqPath = requestPath[0]
}
- return api.registerResourceRoute(favPath, reqPath, h)
-}
-
-// StaticWeb returns a handler that serves HTTP requests
-// with the contents of the file system rooted at directory.
-//
-// first parameter: the route path
-// second parameter: the system directory
-//
-// for more options look router.StaticHandler.
-//
-// api.StaticWeb("/static", "./static")
-//
-// As a special case, the returned file server redirects any request
-// ending in "/index.html" to the same path, without the final
-// "/index.html", if `index.html` should be served then register a
-// new route for it, i.e
-// `app.Get("/static", func(ctx iris.Context){ ctx.ServeFile("./static/index.html", false) })`.
-//
-// StaticWeb calls the `StripPrefix(fullpath, NewStaticHandlerBuilder(systemPath).Listing(false).Build())`.
-//
-// Returns the GET *Route.
-func (api *APIBuilder) StaticWeb(requestPath string, systemPath string) *Route {
- fullpath := joinPath(api.relativePath, requestPath)
-
- // if subdomain,
- // here we get the full path of the path only,
- // because a subdomain can have parties as well
- // and we need that path to call the `StripPrefix`.
- _, fullpath = splitSubdomainAndPath(fullpath)
-
- paramName := "file"
- requestPath = joinPath(requestPath, WildcardParam(paramName))
-
- h := NewStaticHandlerBuilder(systemPath).Listing(false).Build()
-
- if fullpath != "/" {
- h = StripPrefix(fullpath, h)
- }
-
- // it handles the subdomain(root Party) of this party as well, if any.
- return api.registerResourceRoute(systemPath, requestPath, h)
+ return api.registerResourceRoute(reqPath, h)
}
// OnErrorCode registers an error http status code
diff --git a/core/router/deprecated.go b/core/router/deprecated.go
new file mode 100644
index 00000000..d530f540
--- /dev/null
+++ b/core/router/deprecated.go
@@ -0,0 +1,89 @@
+package router
+
+import (
+ "runtime"
+ "strings"
+
+ "github.com/kataras/iris/context"
+)
+
+/*
+ Relative to deprecation:
+ - party.go#L138-154
+ - deprecated_example_test.go
+*/
+
+// https://golang.org/doc/go1.9#callersframes
+func getCaller() (string, int) {
+ var pcs [32]uintptr
+ n := runtime.Callers(1, pcs[:])
+ frames := runtime.CallersFrames(pcs[:n])
+
+ for {
+ frame, more := frames.Next()
+
+ if (!strings.Contains(frame.File, "github.com/kataras/iris") ||
+ strings.Contains(frame.File, "github.com/kataras/iris/_examples") ||
+ strings.Contains(frame.File, "github.com/iris-contrib/examples") ||
+ (strings.Contains(frame.File, "github.com/kataras/iris/core/router") && !strings.Contains(frame.File, "deprecated.go"))) &&
+ !strings.HasSuffix(frame.Func.Name(), ".getCaller") && !strings.Contains(frame.File, "/go/src/testing") {
+ return frame.File, frame.Line
+ }
+
+ if !more {
+ break
+ }
+ }
+
+ return "?", 0
+}
+
+// StaticWeb is DEPRECATED. Use HandleDir(requestPath, directory) instead.
+func (api *APIBuilder) StaticWeb(requestPath string, directory string) *Route {
+ file, line := getCaller()
+ api.reporter.Add(`StaticWeb is DEPRECATED and it will be removed eventually.
+Source: %s:%d
+Use .HandleDir("%s", "%s") instead.`, file, line, requestPath, directory)
+
+ return nil
+}
+
+// StaticHandler is DEPRECATED.
+// Use iris.FileServer(directory, iris.DirOptions{ShowList: true, Gzip: true}) instead.
+//
+// Example https://github.com/kataras/iris/tree/master/_examples/file-server/basic
+func (api *APIBuilder) StaticHandler(directory string, showList bool, gzip bool) context.Handler {
+ file, line := getCaller()
+ api.reporter.Add(`StaticHandler is DEPRECATED and it will be removed eventually.
+Source: %s:%d
+Use iris.FileServer("%s", iris.DirOptions{ShowList: %v, Gzip: %v}) instead.`, file, line, directory, showList, gzip)
+ return FileServer(directory, DirOptions{ShowList: showList, Gzip: gzip})
+}
+
+// StaticEmbedded is DEPRECATED.
+// Use HandleDir(requestPath, directory, iris.DirOptions{Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.
+//
+// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-files-into-app
+func (api *APIBuilder) StaticEmbedded(requestPath string, directory string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route {
+ file, line := getCaller()
+ api.reporter.Add(`StaticEmbedded is DEPRECATED and it will be removed eventually.
+It is also miss the AssetInfo bindata function, which is required now.
+Source: %s:%d
+Use .HandleDir("%s", "%s", iris.DirOptions{Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.`, file, line, requestPath, directory)
+
+ return nil
+}
+
+// StaticEmbeddedGzip is DEPRECATED.
+// Use HandleDir(requestPath, directory, iris.DirOptions{Gzip: true, Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.
+//
+// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-gziped-files-into-app
+func (api *APIBuilder) StaticEmbeddedGzip(requestPath string, directory string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route {
+ file, line := getCaller()
+ api.reporter.Add(`StaticEmbeddedGzip is DEPRECATED and it will be removed eventually.
+It is also miss the AssetInfo bindata function, which is required now.
+Source: %s:%d
+Use .HandleDir("%s", "%s", iris.DirOptions{Gzip: true, Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.`, file, line, requestPath, directory)
+
+ return nil
+}
diff --git a/core/router/deprecated_example_test.go b/core/router/deprecated_example_test.go
new file mode 100644
index 00000000..bfab73da
--- /dev/null
+++ b/core/router/deprecated_example_test.go
@@ -0,0 +1,67 @@
+package router
+
+import (
+ "fmt"
+)
+
+func ExampleParty_StaticWeb() {
+ api := NewAPIBuilder()
+ api.StaticWeb("/static", "./assets")
+
+ err := api.GetReport()
+ if err == nil {
+ panic("expected report for deprecation")
+ }
+
+ fmt.Print(err)
+ // Output: StaticWeb is DEPRECATED and it will be removed eventually.
+ // Source: C:/mygopath/src/github.com/kataras/iris/core/router/deprecated_example_test.go:9
+ // Use .HandleDir("/static", "./assets") instead.
+}
+
+func ExampleParty_StaticHandler() {
+ api := NewAPIBuilder()
+ api.StaticHandler("./assets", false, true)
+
+ err := api.GetReport()
+ if err == nil {
+ panic("expected report for deprecation")
+ }
+
+ fmt.Print(err)
+ // Output: StaticHandler is DEPRECATED and it will be removed eventually.
+ // Source: C:/mygopath/src/github.com/kataras/iris/core/router/deprecated_example_test.go:24
+ // Use iris.FileServer("./assets", iris.DirOptions{ShowList: false, Gzip: true}) instead.
+}
+
+func ExampleParty_StaticEmbedded() {
+ api := NewAPIBuilder()
+ api.StaticEmbedded("/static", "./assets", nil, nil)
+
+ err := api.GetReport()
+ if err == nil {
+ panic("expected report for deprecation")
+ }
+
+ fmt.Print(err)
+ // Output: StaticEmbedded is DEPRECATED and it will be removed eventually.
+ // It is also miss the AssetInfo bindata function, which is required now.
+ // Source: C:/mygopath/src/github.com/kataras/iris/core/router/deprecated_example_test.go:39
+ // Use .HandleDir("/static", "./assets", iris.DirOptions{Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.
+}
+
+func ExampleParty_StaticEmbeddedGzip() {
+ api := NewAPIBuilder()
+ api.StaticEmbeddedGzip("/static", "./assets", nil, nil)
+
+ err := api.GetReport()
+ if err == nil {
+ panic("expected report for deprecation")
+ }
+
+ fmt.Print(err)
+ // Output: StaticEmbeddedGzip is DEPRECATED and it will be removed eventually.
+ // It is also miss the AssetInfo bindata function, which is required now.
+ // Source: C:/mygopath/src/github.com/kataras/iris/core/router/deprecated_example_test.go:55
+ // Use .HandleDir("/static", "./assets", iris.DirOptions{Gzip: true, Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.
+}
diff --git a/core/router/fs.go b/core/router/fs.go
index 721e2965..dbe7815a 100644
--- a/core/router/fs.go
+++ b/core/router/fs.go
@@ -1,45 +1,198 @@
package router
import (
- "errors"
+ "bytes"
"fmt"
"io"
"io/ioutil"
- "mime/multipart"
"net/http"
- "net/textproto"
"net/url"
"os"
"path"
"path/filepath"
"sort"
- "strconv"
"strings"
- "sync"
"time"
"github.com/kataras/iris/context"
)
-// StaticEmbeddedHandler returns a Handler which can serve embedded files
-// that are embedded using the go-bindata tool(assetsGziped = false) or the kataras/bindata tool (assetsGziped = true).
-//
-// Examples: https://github.com/kataras/iris/tree/master/_examples/file-server
-func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string, assetsGziped bool) context.Handler {
- // Depends on the command the user gave to the go-bindata
- // the assset path (names) may be or may not be prepended with a slash.
- // What we do: we remove the ./ from the vdir which should be
- // the same with the asset path (names).
- // we don't pathclean, because that will prepend a slash
- // go-bindata should give a correct path format.
- // On serve time we check the "paramName" (which is the path after the "requestPath")
- // so it has the first directory part missing, we use the "vdir" to complete it
- // and match with the asset path (names).
- if len(vdir) > 0 {
+const indexName = "/index.html"
+
+type DirOptions struct {
+ // Defaults to "/index.html", if request path is ending with **/*/$IndexName
+ // then it redirects to **/*(/) which another handler is handling it,
+ // that another handler, called index handler, is auto-registered by the framework
+ // if end developer does not managed to handle it by hand.
+ IndexName string
+ // When files should served under compression.
+ Gzip bool
+
+ // List the files inside the current requested directory if `IndexName` not found.
+ ShowList bool
+ // If `ShowList` is true then this function will be used instead of the default one to show the list of files of a current requested directory(dir).
+ DirList func(ctx context.Context, dirName string, dir http.File) error
+
+ // When embedded.
+ Asset func(name string) ([]byte, error) // we need this to make it compatible os.File.
+ AssetInfo func(name string) (os.FileInfo, error) // we need this for range support on embedded files.
+ AssetNames func() []string // called once.
+
+ // Optional validator that loops through each requested resource.
+ AssetValidator func(ctx context.Context, name string) bool
+}
+
+func getDirOptions(opts ...DirOptions) (options DirOptions) {
+ if len(opts) > 0 {
+ options = opts[0]
+ }
+
+ if options.IndexName == "" {
+ options.IndexName = indexName
+ } else {
+ options.IndexName = prefix(options.IndexName, "/")
+ }
+
+ return
+}
+
+type embeddedFile struct {
+ os.FileInfo
+ io.ReadSeeker
+}
+
+var _ http.File = (*embeddedFile)(nil)
+
+func (f *embeddedFile) Close() error {
+ return nil
+}
+
+// func (f *embeddedFile) Readdir(count int) ([]os.FileInfo, error) {
+// // this should never happen, show dirs is already checked on the handler level before this call.
+// if count != -1 {
+// return nil, nil
+// }
+
+// list := make([]os.FileInfo, len(f.dir.assetNames))
+// var err error
+// for i, name := range f.dir.assetNames {
+// list[i], err = f.dir.assetInfo(name)
+// if err != nil {
+// return nil, err
+// }
+// }
+// return list, nil
+// }
+
+func (f *embeddedFile) Readdir(count int) ([]os.FileInfo, error) {
+ return nil, nil // should never happen, read directories is done by `embeddedDir`.
+}
+
+func (f *embeddedFile) Stat() (os.FileInfo, error) {
+ return f.FileInfo, nil
+}
+
+// func (f *embeddedFile) Name() string {
+// return strings.TrimLeft(f.vdir, f.FileInfo.Name())
+// }
+
+type embeddedFileSystem struct {
+ vdir string
+ dirNames map[string]*embeddedDir // embedded tools doesn't give that info, so we initialize it in order to support `ShowList` on embedded files as well.
+
+ asset func(name string) ([]byte, error)
+ assetInfo func(name string) (os.FileInfo, error)
+ assetNames []string
+}
+
+var _ http.FileSystem = (*embeddedFileSystem)(nil)
+
+func (fs *embeddedFileSystem) Open(name string) (http.File, error) {
+ // name = fs.vdir + name <- no need, check the TrimLeft(name, vdir) on names loop and the asset and assetInfo redefined on `HandleDir`.
+
+ if d, ok := fs.dirNames[name]; ok {
+ return d, nil
+ }
+
+ info, err := fs.assetInfo(name)
+ if err != nil {
+ return nil, err
+ }
+ b, err := fs.asset(name)
+ if err != nil {
+ return nil, err
+ }
+ return &embeddedFile{
+ FileInfo: info,
+ ReadSeeker: bytes.NewReader(b),
+ }, nil
+}
+
+type embeddedBaseFileInfo struct {
+ baseName string
+ os.FileInfo
+}
+
+func (info *embeddedBaseFileInfo) Name() string {
+ return info.baseName
+}
+
+type embeddedDir struct {
+ name string
+ modTimeUnix int64
+ list []os.FileInfo
+ *bytes.Reader // never used, will always be nil.
+}
+
+var _ http.File = (*embeddedDir)(nil)
+
+func (f *embeddedDir) Close() error { return nil }
+func (f *embeddedDir) Name() string { return f.name }
+func (f *embeddedDir) Size() int64 { return 0 }
+func (f *embeddedDir) Mode() os.FileMode { return os.ModeDir }
+func (f *embeddedDir) ModTime() time.Time { return time.Unix(f.modTimeUnix, 0) }
+func (f *embeddedDir) IsDir() bool { return true }
+func (f *embeddedDir) Sys() interface{} { return f }
+func (f *embeddedDir) Stat() (os.FileInfo, error) { return f, nil }
+
+func (f *embeddedDir) Readdir(count int) ([]os.FileInfo, error) {
+ // this should never happen, show dirs is already checked on the handler level before this call.
+ if count != -1 {
+ return nil, nil
+ }
+
+ return f.list, nil
+}
+
+// Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server
+func FileServer(directory string, opts ...DirOptions) context.Handler {
+ if directory == "" {
+ panic("FileServer: directory is empty. The directory parameter should point to a physical system directory or to an embedded one")
+ }
+
+ options := getDirOptions(opts...)
+
+ // `embeddedFileSystem` (if AssetInfo, Asset and AssetNames are defined) or `http.Dir`.
+ var fs http.FileSystem = http.Dir(directory)
+
+ if options.Asset != nil && options.AssetInfo != nil && options.AssetNames != nil {
+ // Depends on the command the user gave to the go-bindata
+ // the assset path (names) may be or may not be prepended with a slash.
+ // What we do: we remove the ./ from the vdir which should be
+ // the same with the asset path (names).
+ // we don't pathclean, because that will prepend a slash
+ // go-bindata should give a correct path format.
+ // On serve time we check the "paramName" (which is the path after the "requestPath")
+ // so it has the first directory part missing, we use the "vdir" to complete it
+ // and match with the asset path (names).
+ vdir := directory
+
if vdir[0] == '.' {
vdir = vdir[1:]
}
- if vdir[0] == '/' || vdir[0] == os.PathSeparator { // second check for /something, (or ./something if we had dot on 0 it will be removed
+
+ // second check for /something, (or ./something if we had dot on 0 it will be removed)
+ if vdir[0] == '/' || vdir[0] == os.PathSeparator {
vdir = vdir[1:]
}
@@ -50,117 +203,285 @@ func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error
if trailingSlashIdx := len(vdir) - 1; vdir[trailingSlashIdx] == '/' {
vdir = vdir[0:trailingSlashIdx]
}
- }
- // collect the names we are care for,
- // because not all Asset used here, we need the vdir's assets.
- allNames := namesFn()
+ // select only the paths that we care;
+ // that have prefix of the directory and
+ // skip any unnecessary the end-dev or the 3rd party tool may set.
+ var names []string
+ for _, name := range options.AssetNames() {
+ // i.e: name = static/css/main.css (including the directory, see `embeddedFileSystem.vdir`)
- var names []string
- for _, path := range allNames {
- // i.e: path = public/css/main.css
+ if !strings.HasPrefix(name, vdir) {
+ continue
+ }
- // check if path is the path name we care for
- if !strings.HasPrefix(path, vdir) {
- continue
+ names = append(names, strings.TrimLeft(name, vdir))
}
- names = append(names, path)
+ if len(names) == 0 {
+ panic("FileServer: zero embedded files")
+ }
+
+ asset := func(name string) ([]byte, error) {
+ return options.Asset(vdir + name)
+ }
+
+ assetInfo := func(name string) (os.FileInfo, error) {
+ return options.AssetInfo(vdir + name)
+ }
+
+ dirNames := make(map[string]*embeddedDir)
+
+ if options.ShowList {
+ // sort filenames by smaller path.
+ sort.Slice(names, func(i, j int) bool {
+ return strings.Count(names[j], "/") > strings.Count(names[i], "/")
+ })
+
+ for _, name := range names {
+ dirName := path.Dir(name)
+ d, ok := dirNames[dirName]
+
+ if !ok {
+ d = &embeddedDir{
+ name: dirName,
+ modTimeUnix: time.Now().Unix(),
+ }
+ dirNames[dirName] = d
+ }
+
+ info, err := assetInfo(name)
+ if err != nil {
+ panic(fmt.Sprintf("FileServer: report as bug: file info: %s not found in: %s", name, dirName))
+ }
+ d.list = append(d.list, &embeddedBaseFileInfo{path.Base(name), info})
+ }
+
+ }
+
+ fs = &embeddedFileSystem{
+ vdir: vdir,
+ dirNames: dirNames,
+
+ asset: asset,
+ assetInfo: assetInfo,
+ assetNames: names,
+ }
+ } else if !DirectoryExists(directory) {
+ panic("FileServer: system directory: " + directory + " does not exist")
}
- // modtime := time.Now()
- h := func(ctx context.Context) {
+ plainStatusCode := func(ctx context.Context, statusCode int) {
+ if writer, ok := ctx.ResponseWriter().(*context.GzipResponseWriter); ok && writer != nil {
+ writer.ResetBody()
+ writer.Disable()
+ }
+ ctx.StatusCode(statusCode)
+ }
- reqPath := strings.TrimPrefix(ctx.Request().URL.Path, "/"+vdir)
- // i.e : /css/main.css
-
- for _, path := range names {
- // in order to map "/" as "/index.html"
- if path == "/index.html" && reqPath == "/" {
- reqPath = "/index.html"
- }
-
- if path != vdir+reqPath {
- continue
- }
-
- cType := TypeByFilename(path)
-
- buf, err := assetFn(path) // remove the first slash
-
- if assetsGziped {
- // this will add the "Vary" : "Accept-Encoding"
- // and "Content-Encoding": "gzip"
- // headers.
- context.AddGzipHeaders(ctx.ResponseWriter())
- }
+ htmlReplacer := strings.NewReplacer(
+ "&", "&",
+ "<", "<",
+ ">", ">",
+ // """ is shorter than """.
+ `"`, """,
+ // "'" is shorter than "'" and apos was not in HTML until HTML5.
+ "'", "'",
+ )
+ dirList := options.DirList
+ if dirList == nil {
+ dirList = func(ctx context.Context, dirName string, dir http.File) error {
+ dirs, err := dir.Readdir(-1)
if err != nil {
- continue
+ return err
}
- ctx.ContentType(cType)
- if _, err := ctx.Write(buf); err != nil {
- ctx.StatusCode(http.StatusInternalServerError)
- ctx.StopExecution()
+ // dst, _ := dir.Stat()
+ // dirName := dst.Name()
+
+ sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
+
+ ctx.ContentType(context.ContentHTMLHeaderValue)
+ ctx.WriteString("\n")
+ for _, d := range dirs {
+ name := d.Name()
+ if d.IsDir() {
+ name += "/"
+ }
+ // name may contain '?' or '#', which must be escaped to remain
+ // part of the URL path, and not indicate the start of a query
+ // string or fragment.
+ url := url.URL{Path: joinPath("./"+dirName, name)} // edit here to redirect correctly, standard library misses that.
+ ctx.Writef("%s\n", url.String(), htmlReplacer.Replace(name))
+ }
+ ctx.WriteString("
\n")
+ return nil
+ }
+ }
+
+ h := func(ctx context.Context) {
+ name := prefix(ctx.Request().URL.Path, "/")
+ ctx.Request().URL.Path = name
+
+ gzip := options.Gzip
+ if !gzip {
+ // if false then check if the dev did something like `ctx.Gzip(true)`.
+ _, gzip = ctx.ResponseWriter().(*context.GzipResponseWriter)
+ }
+
+ f, err := fs.Open(name)
+ if err != nil {
+ plainStatusCode(ctx, http.StatusNotFound)
+ return
+ }
+ defer f.Close()
+
+ info, err := f.Stat()
+ if err != nil {
+ plainStatusCode(ctx, http.StatusNotFound)
+ return
+ }
+
+ // use contents of index.html for directory, if present
+ if info.IsDir() && options.IndexName != "" {
+ // Note that, in contrast of the default net/http mechanism;
+ // here different handlers may serve the indexes
+ // if manually then this will block will never fire,
+ // if index handler are automatically registered by the framework
+ // then this block will be fired on indexes because the static site routes are registered using the static route's handler.
+ //
+ // End-developers must have the chance to register different logic and middlewares
+ // to an index file, useful on Single Page Applications.
+
+ index := strings.TrimSuffix(name, "/") + options.IndexName
+ fIndex, err := fs.Open(index)
+ if err == nil {
+ defer fIndex.Close()
+ infoIndex, err := fIndex.Stat()
+ if err == nil {
+ info = infoIndex
+ f = fIndex
+ }
+ }
+ }
+
+ // Still a directory? (we didn't find an index.html file)
+ if info.IsDir() {
+ if !options.ShowList {
+ plainStatusCode(ctx, http.StatusNotFound)
+ return
+ }
+ if modified, err := ctx.CheckIfModifiedSince(info.ModTime()); !modified && err == nil {
+ ctx.WriteNotModified()
+ ctx.StatusCode(http.StatusNotModified)
+ ctx.Next()
+ return
+ }
+ ctx.SetLastModified(info.ModTime())
+ err = dirList(ctx, info.Name(), f)
+ if err != nil {
+ plainStatusCode(ctx, http.StatusInternalServerError)
+ return
+ }
+
+ ctx.Next()
+ return
+ }
+
+ // index requested, send a moved permanently status
+ // and navigate back to the route without the index suffix.
+ if strings.HasSuffix(name, options.IndexName) {
+ localRedirect(ctx, "./")
+ return
+ }
+
+ if options.AssetValidator != nil {
+ if !options.AssetValidator(ctx, info.Name()) {
+ errCode := ctx.GetStatusCode()
+ if ctx.ResponseWriter().Written() <= context.StatusCodeWritten {
+ // if nothing written as body from the AssetValidator but 200 status code(which is the default),
+ // then we assume that the end-developer just returned false expecting this to be not found.
+ if errCode == http.StatusOK {
+ errCode = http.StatusNotFound
+ }
+ plainStatusCode(ctx, errCode)
+ }
+ return
+ }
+ }
+
+ // try to find and send the correct content type based on the filename
+ // and the binary data inside "f".
+ detectOrWriteContentType(ctx, info.Name(), f)
+
+ if gzip {
+ // set the last modified as "serveContent" does.
+ ctx.SetLastModified(info.ModTime())
+
+ // write the file to the response writer.
+ contents, err := ioutil.ReadAll(f)
+ if err != nil {
+ ctx.Application().Logger().Debugf("err reading file: %v", err)
+ plainStatusCode(ctx, http.StatusInternalServerError)
+ return
+ }
+
+ // Use `WriteNow` instead of `Write`
+ // because we need to know the compressed written size before
+ // the `FlushResponse`.
+ _, err = ctx.GzipResponseWriter().Write(contents)
+ if err != nil {
+ ctx.Application().Logger().Debugf("short write: %v", err)
+ plainStatusCode(ctx, http.StatusInternalServerError)
+ return
}
return
}
- // not found or error
- ctx.NotFound()
+ http.ServeContent(ctx.ResponseWriter(), ctx.Request(), info.Name(), info.ModTime(), f)
+ if serveCode := ctx.GetStatusCode(); context.StatusCodeNotSuccessful(serveCode) {
+ plainStatusCode(ctx, serveCode)
+ return
+ }
+
+ ctx.Next() // fire any middleware, if any.
}
return h
}
-// StaticHandler returns a new Handler which is ready
-// to serve all kind of static files.
-//
-// Developers can wrap this handler using the `router.StripPrefix`
-// for a fixed static path when the result handler is being, finally, registered to a route.
-//
+// StripPrefix returns a handler that serves HTTP requests
+// by removing the given prefix from the request URL's Path
+// and invoking the handler h. StripPrefix handles a
+// request for a path that doesn't begin with prefix by
+// replying with an HTTP 404 not found error.
//
// Usage:
-// app := iris.New()
-// ...
-// fileserver := iris.StaticHandler("./static_files", false, false)
-// h := router.StripPrefix("/static", fileserver)
-// /* http://mydomain.com/static/css/style.css */
-// app.Get("/static/{file:path}", h)
-// ...
-//
-func StaticHandler(systemPath string, showList bool, gzip bool) context.Handler {
- return NewStaticHandlerBuilder(systemPath).
- Gzip(gzip).
- Listing(showList).
- Build()
-}
+// fileserver := FileServer("./static_files", DirOptions {...})
+// h := StripPrefix("/static", fileserver)
+// app.Get("/static/{f:path}", h)
+// app.Head("/static/{f:path}", h)
+func StripPrefix(prefix string, h context.Handler) context.Handler {
+ if prefix == "" {
+ return h
+ }
+ // here we separate the path from the subdomain (if any), we care only for the path
+ // fixes a bug when serving static files via a subdomain
+ canonicalPrefix := prefix
+ if dotWSlashIdx := strings.Index(canonicalPrefix, SubdomainPrefix); dotWSlashIdx > 0 {
+ canonicalPrefix = canonicalPrefix[dotWSlashIdx+1:]
+ }
+ canonicalPrefix = toWebPath(canonicalPrefix)
-// StaticHandlerBuilder is the web file system's Handler builder
-// use that or the iris.StaticHandler/StaticWeb methods.
-type StaticHandlerBuilder interface {
- Gzip(enable bool) StaticHandlerBuilder
- Listing(listDirectoriesOnOff bool) StaticHandlerBuilder
- Build() context.Handler
-}
-
-// +------------------------------------------------------------+
-// | |
-// | Static Builder |
-// | |
-// +------------------------------------------------------------+
-
-type fsHandler struct {
- // user options, only directory is required.
- directory http.Dir
- listDirectories bool
- gzip bool
- // these are init on the Build() call
- filesystem http.FileSystem
- once sync.Once
- handler context.Handler
- begin context.Handlers
+ return func(ctx context.Context) {
+ if p := strings.TrimPrefix(ctx.Request().URL.Path, canonicalPrefix); len(p) < len(ctx.Request().URL.Path) {
+ ctx.Request().URL.Path = p
+ h(ctx)
+ } else {
+ ctx.NotFound()
+ }
+ }
}
func toWebPath(systemPath string) string {
@@ -183,215 +504,6 @@ func Abs(path string) string {
return absPath
}
-// NewStaticHandlerBuilder returns a new Handler which serves static files
-// supports gzip, no listing and much more
-// Note that, this static builder returns a Handler
-// it doesn't cares about the rest of your iris configuration.
-//
-// Use the iris.StaticHandler/StaticWeb in order to serve static files on more automatic way
-// this builder is used by people who have more complicated application
-// structure and want a fluent api to work on.
-func NewStaticHandlerBuilder(dir string) StaticHandlerBuilder {
- return &fsHandler{
- directory: http.Dir(Abs(dir)),
- // list directories disabled by default
- listDirectories: false,
- }
-}
-
-// Gzip if enable is true then gzip compression is enabled for this static directory.
-//
-// Defaults to false.
-func (w *fsHandler) Gzip(enable bool) StaticHandlerBuilder {
- w.gzip = enable
- return w
-}
-
-// Listing turn on/off the 'show files and directories'.
-//
-// Defaults to false.
-func (w *fsHandler) Listing(listDirectoriesOnOff bool) StaticHandlerBuilder {
- w.listDirectories = listDirectoriesOnOff
- return w
-}
-
-type (
- noListFile struct {
- http.File
- }
-)
-
-// Overrides the Readdir of the http.File in order to disable showing a list of the dirs/files
-func (n noListFile) Readdir(count int) ([]os.FileInfo, error) {
- return nil, nil
-}
-
-// Implements the http.Filesystem
-// Do not call it.
-func (w *fsHandler) Open(name string) (http.File, error) {
- info, err := w.filesystem.Open(name)
-
- if err != nil {
- return nil, err
- }
-
- if !w.listDirectories {
- return noListFile{info}, nil
- }
-
- return info, nil
-}
-
-// Build the handler (once) and returns it
-func (w *fsHandler) Build() context.Handler {
- // we have to ensure that Build is called ONLY one time,
- // one instance per one static directory.
- w.once.Do(func() {
- w.filesystem = w.directory
-
- fileserver := func(ctx context.Context) {
- upath := ctx.Request().URL.Path
- if !strings.HasPrefix(upath, "/") {
- upath = "/" + upath
- ctx.Request().URL.Path = upath
- }
-
- // Note the request.url.path is changed but request.RequestURI is not
- // so on custom errors we use the requesturi instead.
- // this can be changed.
-
- // take the gzip setting.
- gzipEnabled := w.gzip
- if !gzipEnabled {
- // if false then check if the dev did something like `ctx.Gzip(true)`.
- _, gzipEnabled = ctx.ResponseWriter().(*context.GzipResponseWriter)
- }
-
- _, prevStatusCode := serveFile(ctx,
- w.filesystem,
- path.Clean(upath),
- false,
- w.listDirectories,
- gzipEnabled)
-
- // check for any http errors after the file handler executed
- if context.StatusCodeNotSuccessful(prevStatusCode) { // error found (404 or 400 or 500 usually)
- if writer, ok := ctx.ResponseWriter().(*context.GzipResponseWriter); ok && writer != nil {
- writer.ResetBody()
- writer.Disable()
- // ctx.ResponseWriter.Header().Del(contentType) // application/x-gzip sometimes lawl
- // remove gzip headers
- // headers := ctx.ResponseWriter.Header()
- // headers[contentType] = nil
- // headers["X-Content-Type-Options"] = nil
- // headers[varyHeader] = nil
- // headers[contentEncodingHeader] = nil
- // headers[contentLength] = nil
- }
- // ctx.Application().Logger().Infof(errMsg)
- ctx.StatusCode(prevStatusCode)
- return
- }
-
- // go to the next middleware, if any.
- ctx.Next()
- }
-
- w.handler = fileserver
- })
-
- return w.handler
-}
-
-// StripPrefix returns a handler that serves HTTP requests
-// by removing the given prefix from the request URL's Path
-// and invoking the handler h. StripPrefix handles a
-// request for a path that doesn't begin with prefix by
-// replying with an HTTP 404 not found error.
-//
-// Usage:
-// fileserver := Party#StaticHandler("./static_files", false, false)
-// h := router.StripPrefix("/static", fileserver)
-// app.Get("/static/{f:path}", h)
-// app.Head("/static/{f:path}", h)
-func StripPrefix(prefix string, h context.Handler) context.Handler {
- if prefix == "" {
- return h
- }
- // here we separate the path from the subdomain (if any), we care only for the path
- // fixes a bug when serving static files via a subdomain
- fixedPrefix := prefix
- if dotWSlashIdx := strings.Index(fixedPrefix, SubdomainPrefix); dotWSlashIdx > 0 {
- fixedPrefix = fixedPrefix[dotWSlashIdx+1:]
- }
- fixedPrefix = toWebPath(fixedPrefix)
-
- return func(ctx context.Context) {
- if p := strings.TrimPrefix(ctx.Request().URL.Path, fixedPrefix); len(p) < len(ctx.Request().URL.Path) {
- ctx.Request().URL.Path = p
- h(ctx)
- } else {
- ctx.NotFound()
- }
- }
-}
-
-// +------------------------------------------------------------+
-// | |
-// | serve file handler |
-// | edited from net/http/fs.go in order to support GZIP with |
-// | custom iris http errors and fallback to non-compressed data|
-// | when not supported. |
-// | |
-// +------------------------------------------------------------+
-
-var htmlReplacer = strings.NewReplacer(
- "&", "&",
- "<", "<",
- ">", ">",
- // """ is shorter than """.
- `"`, """,
- // "'" is shorter than "'" and apos was not in HTML until HTML5.
- "'", "'",
-)
-
-func dirList(ctx context.Context, f http.File) (string, int) {
- dirs, err := f.Readdir(-1)
- if err != nil {
- // TODO: log err.Error() to the Server.ErrorLog, once it's possible
- // for a handler to get at its Server via the http.ResponseWriter. See
- // Issue 12438.
- return "Error reading directory", http.StatusInternalServerError
-
- }
- sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
- ctx.ContentType("text/html")
- fmt.Fprintf(ctx.ResponseWriter(), "\n")
- for _, d := range dirs {
- name := d.Name()
- if d.IsDir() {
- name += "/"
- }
- // name may contain '?' or '#', which must be escaped to remain
- // part of the URL path, and not indicate the start of a query
- // string or fragment.
- url := url.URL{Path: name}
- fmt.Fprintf(ctx.ResponseWriter(), "%s\n", url.String(), htmlReplacer.Replace(name))
- }
- fmt.Fprintf(ctx.ResponseWriter(), "
\n")
- return "", http.StatusOK
-}
-
-// errSeeker is returned by ServeContent's sizeFunc when the content
-// doesn't seek properly. The underlying Seeker's error text isn't
-// included in the sizeFunc reply so it's not sent over HTTP to end
-// users.
-var errSeeker = errors.New("seeker can't seek")
-
-// errNoOverlap is returned by serveContent's parseRange if first-byte-pos of
-// all of the byte-range-spec values is greater than the content size.
-var errNoOverlap = errors.New("invalid range: failed to overlap")
-
// The algorithm uses at most sniffLen bytes to make its decision.
const sniffLen = 512
@@ -422,442 +534,6 @@ func detectOrWriteContentType(ctx context.Context, name string, content io.ReadS
return ctype, nil
}
-// if name is empty, filename is unknown. (used for mime type, before sniffing)
-// if modtime.IsZero(), modtime is unknown.
-// 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*/ {
- ctx.SetLastModified(modtime)
- done, rangeReq := checkPreconditions(ctx, modtime)
- if done {
- return "", http.StatusNotModified
- }
-
- code := http.StatusOK
-
- // If Content-Type isn't set, use the file's extension to find it, but
- // if the Content-Type is unset explicitly, do not sniff the type.
- ctype, err := detectOrWriteContentType(ctx, name, content)
- if err != nil {
- return "while seeking", http.StatusInternalServerError
- }
-
- size, err := sizeFunc()
- if err != nil {
- return err.Error(), http.StatusInternalServerError
- }
-
- // handle Content-Range header.
- sendSize := size
- var sendContent io.Reader = content
-
- if size >= 0 {
- ranges, err := parseRange(rangeReq, size)
- if err != nil {
- if err == errNoOverlap {
- ctx.Header("Content-Range", fmt.Sprintf("bytes */%d", size))
- }
- return err.Error(), http.StatusRequestedRangeNotSatisfiable
-
- }
- if sumRangesSize(ranges) > size {
- // The total number of bytes in all the ranges
- // is larger than the size of the file by
- // itself, so this is probably an attack, or a
- // dumb client. Ignore the range request.
- ranges = nil
- }
- switch {
- case len(ranges) == 1:
- // RFC 2616, Section 14.16:
- // "When an HTTP message includes the content of a single
- // range (for example, a response to a request for a
- // single range, or to a request for a set of ranges
- // that overlap without any holes), this content is
- // transmitted with a Content-Range header, and a
- // Content-Length header showing the number of bytes
- // actually transferred.
- // ...
- // A response to a request for a single range MUST NOT
- // be sent using the multipart/byteranges media type."
- ra := ranges[0]
- if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
- return err.Error(), http.StatusRequestedRangeNotSatisfiable
- }
- sendSize = ra.length
- code = http.StatusPartialContent
- ctx.Header("Content-Range", ra.contentRange(size))
- case len(ranges) > 1:
- sendSize = rangesMIMESize(ranges, ctype, size)
- code = http.StatusPartialContent
-
- pr, pw := io.Pipe()
- mw := multipart.NewWriter(pw)
- ctx.ContentType("multipart/byteranges; boundary=" + mw.Boundary())
- sendContent = pr
- defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
- go func() {
- for _, ra := range ranges {
- part, err := mw.CreatePart(ra.mimeHeader(ctype, size))
- if err != nil {
- pw.CloseWithError(err)
- return
- }
- if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
- pw.CloseWithError(err)
- return
- }
- if _, err := io.CopyN(part, content, ra.length); err != nil {
- pw.CloseWithError(err)
- return
- }
- }
- mw.Close()
- pw.Close()
- }()
- }
- ctx.Header("Accept-Ranges", "bytes")
- if ctx.ResponseWriter().Header().Get(context.ContentEncodingHeaderKey) == "" {
- ctx.Header(context.ContentLengthHeaderKey, strconv.FormatInt(sendSize, 10))
- }
- }
-
- ctx.StatusCode(code)
-
- if ctx.Method() != http.MethodHead {
- io.CopyN(ctx.ResponseWriter(), sendContent, sendSize)
- }
-
- 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 "", "".
-func scanETag(s string) (etag string, remain string) {
- s = textproto.TrimString(s)
- start := 0
- if strings.HasPrefix(s, "W/") {
- start = 2
- }
- if len(s[start:]) < 2 || s[start] != '"' {
- return "", ""
- }
- // ETag is either W/"text" or "text".
- // See RFC 7232 2.3.
- for i := start + 1; i < len(s); i++ {
- c := s[i]
- switch {
- // Character values allowed in ETags.
- case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80:
- case c == '"':
- return string(s[:i+1]), s[i+1:]
- default:
- break
- }
- }
- return "", ""
-}
-
-// etagStrongMatch reports whether a and b match using strong ETag comparison.
-// Assumes a and b are valid ETags.
-func etagStrongMatch(a, b string) bool {
- return a == b && a != "" && a[0] == '"'
-}
-
-// etagWeakMatch reports whether a and b match using weak ETag comparison.
-// Assumes a and b are valid ETags.
-func etagWeakMatch(a, b string) bool {
- return strings.TrimPrefix(a, "W/") == strings.TrimPrefix(b, "W/")
-}
-
-// condResult is the result of an HTTP request precondition check.
-// See https://tools.ietf.org/html/rfc7232 section 3.
-type condResult int
-
-const (
- condNone condResult = iota
- condTrue
- condFalse
-)
-
-func checkIfMatch(ctx context.Context) condResult {
- im := ctx.GetHeader("If-Match")
- if im == "" {
- return condNone
- }
- for {
- im = textproto.TrimString(im)
- if len(im) == 0 {
- break
- }
- if im[0] == ',' {
- im = im[1:]
- continue
- }
- if im[0] == '*' {
- return condTrue
- }
- etag, remain := scanETag(im)
- if etag == "" {
- break
- }
- if etagStrongMatch(etag, ctx.ResponseWriter().Header().Get("Etag")) {
- return condTrue
- }
- im = remain
- }
-
- return condFalse
-}
-
-func checkIfNoneMatch(ctx context.Context) condResult {
- inm := ctx.GetHeader("If-None-Match")
- if inm == "" {
- return condNone
- }
- buf := inm
- for {
- buf = textproto.TrimString(buf)
- if len(buf) == 0 {
- break
- }
- if buf[0] == ',' {
- buf = buf[1:]
- }
- if buf[0] == '*' {
- return condFalse
- }
- etag, remain := scanETag(buf)
- if etag == "" {
- break
- }
- if etagWeakMatch(etag, ctx.ResponseWriter().Header().Get("Etag")) {
- return condFalse
- }
- buf = remain
- }
- return condTrue
-}
-
-// 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) {
- // This function carefully follows RFC 7232 section 6.
- ch := checkIfMatch(ctx)
- if ch == condNone {
- ch = checkIfUnmodifiedSince(ctx, modtime)
- }
- if ch == condFalse {
-
- ctx.StatusCode(http.StatusPreconditionFailed)
- return true, ""
- }
- switch checkIfNoneMatch(ctx) {
- case condFalse:
- if ctx.Method() == http.MethodGet || ctx.Method() == http.MethodHead {
- ctx.WriteNotModified()
- return true, ""
- }
- ctx.StatusCode(http.StatusPreconditionFailed)
- return true, ""
-
- case condNone:
- if modified, err := ctx.CheckIfModifiedSince(modtime); !modified && err == nil {
- ctx.WriteNotModified()
- return true, ""
- }
- }
-
- rangeHeader = ctx.GetHeader("Range")
- if rangeHeader != "" {
- 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
-}
-
-const indexPage = "/index.html"
-
-// name is '/'-separated, not filepath.Separator.
-func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bool, showList bool, gzip bool) (string, int) {
- // redirect .../index.html to .../
- // can't use Redirect() because that would make the path absolute,
- // which would be a problem running under StripPrefix
- if strings.HasSuffix(ctx.Request().URL.Path, indexPage) {
- localRedirect(ctx, "./")
- return "", http.StatusMovedPermanently
- }
-
- f, err := fs.Open(name)
- if err != nil {
- return err.Error(), 404
- }
- defer f.Close()
-
- d, err := f.Stat()
- if err != nil {
- return err.Error(), 404
- }
-
- if redirect {
- // redirect to canonical path: / at end of directory url
- // ctx.Request.URL.Path always begins with /
- url := ctx.Request().URL.Path
- if d.IsDir() {
- if url[len(url)-1] != '/' {
- localRedirect(ctx, path.Base(url)+"/")
- return "", http.StatusMovedPermanently
- }
- } else {
- if url[len(url)-1] == '/' {
- localRedirect(ctx, "../"+path.Base(url))
- return "", http.StatusMovedPermanently
- }
- }
- }
-
- // redirect if the directory name doesn't end in a slash
- if d.IsDir() {
- url := ctx.Request().URL.Path
- if url[len(url)-1] != '/' {
- localRedirect(ctx, path.Base(url)+"/")
- return "", http.StatusMovedPermanently
- }
- }
-
- // use contents of index.html for directory, if present
- if d.IsDir() {
- index := strings.TrimSuffix(name, "/") + indexPage
- ff, err := fs.Open(index)
- if err == nil {
- defer ff.Close()
- dd, err := ff.Stat()
- if err == nil {
- d = dd
- f = ff
- }
- }
- }
-
- // Still a directory? (we didn't find an index.html file)
- if d.IsDir() {
- if !showList {
- return "", http.StatusForbidden
- }
- if modified, err := ctx.CheckIfModifiedSince(d.ModTime()); !modified && err == nil {
- ctx.WriteNotModified()
- return "", http.StatusNotModified
- }
- ctx.SetLastModified(d.ModTime())
- return dirList(ctx, f)
- }
-
- // if gzip disabled then continue using content byte ranges
- if !gzip {
- // serveContent will check modification time
- sizeFunc := func() (int64, error) { return d.Size(), nil }
- return serveContent(ctx, d.Name(), d.ModTime(), sizeFunc, f)
- }
-
- // else, set the last modified as "serveContent" does.
- ctx.SetLastModified(d.ModTime())
-
- // write the file to the response writer.
- contents, err := ioutil.ReadAll(f)
- if err != nil {
- ctx.Application().Logger().Debugf("err reading file: %v", err)
- return "error reading the file", http.StatusInternalServerError
- }
-
- // Use `WriteNow` instead of `Write`
- // because we need to know the compressed written size before
- // the `FlushResponse`.
- _, err = ctx.GzipResponseWriter().Write(contents)
- if err != nil {
- ctx.Application().Logger().Debugf("short write: %v", err)
- return "short write", http.StatusInternalServerError
- }
-
- // try to find and send the correct content type based on the filename
- // and the binary data inside "f".
- detectOrWriteContentType(ctx, d.Name(), f)
-
- return "", http.StatusOK
-}
-
-// toHTTPError returns a non-specific HTTP error message and status code
-// for a given non-nil error value. It's important that toHTTPError does not
-// actually return err.Error(), since msg and httpStatus are returned to users,
-// and historically Go's ServeContent always returned just "404 Not Found" for
-// all errors. We don't want to start leaking information in error messages.
-func toHTTPError(err error) (msg string, httpStatus int) {
- if os.IsNotExist(err) {
- return "404 page not found", http.StatusNotFound
- }
- if os.IsPermission(err) {
- return "403 Forbidden", http.StatusForbidden
- }
- // Default:
- return "500 Internal Server Error", http.StatusInternalServerError
-}
-
// localRedirect gives a Moved Permanently response.
// It does not convert relative paths to absolute paths like Redirect does.
func localRedirect(ctx context.Context, newPath string) {
@@ -869,135 +545,6 @@ func localRedirect(ctx context.Context, newPath string) {
ctx.StatusCode(http.StatusMovedPermanently)
}
-func containsDotDot(v string) bool {
- if !strings.Contains(v, "..") {
- return false
- }
- for _, ent := range strings.FieldsFunc(v, isSlashRune) {
- if ent == ".." {
- return true
- }
- }
- return false
-}
-
-func isSlashRune(r rune) bool { return r == '/' || r == '\\' }
-
-// httpRange specifies the byte range to be sent to the client.
-type httpRange struct {
- start, length int64
-}
-
-func (r httpRange) contentRange(size int64) string {
- return fmt.Sprintf("bytes %d-%d/%d", r.start, r.start+r.length-1, size)
-}
-
-func (r httpRange) mimeHeader(contentType string, size int64) textproto.MIMEHeader {
- return textproto.MIMEHeader{
- "Content-Range": {r.contentRange(size)},
- contentType: {contentType},
- }
-}
-
-// parseRange parses a Range header string as per RFC 2616.
-// errNoOverlap is returned if none of the ranges overlap.
-func parseRange(s string, size int64) ([]httpRange, error) {
- if s == "" {
- return nil, nil // header not present
- }
- const b = "bytes="
- if !strings.HasPrefix(s, b) {
- return nil, errors.New("invalid range")
- }
- var ranges []httpRange
- noOverlap := false
- for _, ra := range strings.Split(s[len(b):], ",") {
- ra = strings.TrimSpace(ra)
- if ra == "" {
- continue
- }
- i := strings.Index(ra, "-")
- if i < 0 {
- return nil, errors.New("invalid range")
- }
- start, end := strings.TrimSpace(ra[:i]), strings.TrimSpace(ra[i+1:])
- var r httpRange
- if start == "" {
- // If no start is specified, end specifies the
- // range start relative to the end of the file.
- i, err := strconv.ParseInt(end, 10, 64)
- if err != nil {
- return nil, errors.New("invalid range")
- }
- if i > size {
- i = size
- }
- r.start = size - i
- r.length = size - r.start
- } else {
- i, err := strconv.ParseInt(start, 10, 64)
- if err != nil || i < 0 {
- return nil, errors.New("invalid range")
- }
- if i >= size {
- // If the range begins after the size of the content,
- // then it does not overlap.
- noOverlap = true
- continue
- }
- r.start = i
- if end == "" {
- // If no end is specified, range extends to end of the file.
- r.length = size - r.start
- } else {
- i, err := strconv.ParseInt(end, 10, 64)
- if err != nil || r.start > i {
- return nil, errors.New("invalid range")
- }
- if i >= size {
- i = size - 1
- }
- r.length = i - r.start + 1
- }
- }
- ranges = append(ranges, r)
- }
- if noOverlap && len(ranges) == 0 {
- // The specified ranges did not overlap with the content.
- return nil, errNoOverlap
- }
- return ranges, nil
-}
-
-// countingWriter counts how many bytes have been written to it.
-type countingWriter int64
-
-func (w *countingWriter) Write(p []byte) (n int, err error) {
- *w += countingWriter(len(p))
- return len(p), nil
-}
-
-// rangesMIMESize returns the number of bytes it takes to encode the
-// provided ranges as a multipart response.
-func rangesMIMESize(ranges []httpRange, contentType string, contentSize int64) (encSize int64) {
- var w countingWriter
- mw := multipart.NewWriter(&w)
- for _, ra := range ranges {
- mw.CreatePart(ra.mimeHeader(contentType, contentSize))
- encSize += ra.length
- }
- mw.Close()
- encSize += int64(w)
- return
-}
-
-func sumRangesSize(ranges []httpRange) (size int64) {
- for _, ra := range ranges {
- size += ra.length
- }
- return
-}
-
// DirectoryExists returns true if a directory(or file) exists, otherwise false
func DirectoryExists(dir string) bool {
if _, err := os.Stat(dir); os.IsNotExist(err) {
diff --git a/core/router/handler.go b/core/router/handler.go
index 97aa8a4d..bcf41cea 100644
--- a/core/router/handler.go
+++ b/core/router/handler.go
@@ -76,11 +76,14 @@ func NewDefaultHandler() RequestHandler {
type RoutesProvider interface { // api builder
GetRoutes() []*Route
GetRoute(routeName string) *Route
+ // GetStaticSites() []*StaticSite
+ // Macros() *macro.Macros
}
func (h *routerHandler) Build(provider RoutesProvider) error {
- registeredRoutes := provider.GetRoutes()
h.trees = h.trees[0:0] // reset, inneed when rebuilding.
+ rp := errors.NewReporter()
+ registeredRoutes := provider.GetRoutes()
// sort, subdomains go first.
sort.Slice(registeredRoutes, func(i, j int) bool {
@@ -111,11 +114,8 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
// the rest are handled inside the node
return lsub1 > lsub2
-
})
- rp := errors.NewReporter()
-
for _, r := range registeredRoutes {
// build the r.Handlers based on begin and done handlers, if any.
r.BuildHandlers()
@@ -133,6 +133,7 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
rp.Add("%v -> %s", err, r.String())
continue
}
+
golog.Debugf(r.Trace())
}
diff --git a/core/router/party.go b/core/router/party.go
index 5d462392..340eb604 100644
--- a/core/router/party.go
+++ b/core/router/party.go
@@ -120,6 +120,39 @@ type Party interface {
// in order to handle more than one paths for the same controller instance.
HandleMany(method string, relativePath string, handlers ...context.Handler) []*Route
+ // HandleDir registers a handler that serves HTTP requests
+ // with the contents of a file system (physical or embedded).
+ //
+ // first parameter : the route path
+ // second parameter : the system or the embedded directory that needs to be served
+ // third parameter : not required, the directory options, set fields is optional.
+ //
+ // for more options look router.FileServer.
+ //
+ // api.HandleDir("/static", "./assets", DirOptions {ShowList: true, Gzip: true, IndexName: "index.html"})
+ //
+ // Returns the GET *Route.
+ //
+ // Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server
+ HandleDir(requestPath, directory string, opts ...DirOptions) *Route
+ // StaticWeb is DEPRECATED. Use HandleDir(requestPath, directory) instead.
+ StaticWeb(requestPath string, directory string) *Route
+ // StaticHandler is DEPRECATED.
+ // Use iris.FileServer(directory, iris.DirOptions{ShowList: true, Gzip: true}) instead.
+ //
+ // Example https://github.com/kataras/iris/tree/master/_examples/file-server/basic
+ StaticHandler(directory string, showList bool, gzip bool) context.Handler
+ // StaticEmbedded is DEPRECATED.
+ // Use HandleDir(requestPath, directory, iris.DirOptions{Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.
+ //
+ // Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-files-into-app
+ StaticEmbedded(requestPath string, directory string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route
+ // StaticEmbeddedGzip is DEPRECATED.
+ // Use HandleDir(requestPath, directory, iris.DirOptions{Gzip: true, Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.
+ //
+ // Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-gziped-files-into-app
+ StaticEmbeddedGzip(requestPath string, directory string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route
+
// None registers an "offline" route
// see context.ExecRoute(routeName) and
// party.Routes().Online(handleResultregistry.*Route, "GET") and
@@ -167,61 +200,11 @@ type Party interface {
// Any registers a route for ALL of the http methods
// (Get,Post,Put,Head,Patch,Options,Connect,Delete).
Any(registeredPath string, handlers ...context.Handler) []*Route
-
- // StaticHandler returns a new Handler which is ready
- // to serve all kind of static files.
- //
- // Note:
- // The only difference from package-level `StaticHandler`
- // is that this `StaticHandler` receives a request path which
- // is appended to the party's relative path and stripped here.
- //
- // Usage:
- // app := iris.New()
- // ...
- // mySubdomainFsServer := app.Party("mysubdomain.")
- // h := mySubdomainFsServer.StaticHandler("./static_files", false, false)
- // /* http://mysubdomain.mydomain.com/static/css/style.css */
- // mySubdomainFsServer.Get("/static", h)
- // ...
- //
- StaticHandler(systemPath string, showList bool, gzip bool) context.Handler
-
- // StaticServe serves a directory as web resource
- // it's the simpliest form of the Static* functions
- // Almost same usage as StaticWeb
- // accepts only one required parameter which is the systemPath,
- // the same path will be used to register the GET and HEAD method routes.
- // If second parameter is empty, otherwise the requestPath is the second parameter
- // it uses gzip compression (compression on each request, no file cache).
- //
- // Returns the GET *Route.
- StaticServe(systemPath string, requestPath ...string) *Route
// StaticContent registers a GET and HEAD method routes to the requestPath
// that are ready to serve raw static bytes, memory cached.
//
// Returns the GET *Route.
StaticContent(requestPath string, cType string, content []byte) *Route
-
- // StaticEmbedded used when files are distributed inside the app executable, using go-bindata mostly
- // First parameter is the request path, the path which the files in the vdir will be served to, for example "/static"
- // Second parameter is the (virtual) directory path, for example "./assets"
- // Third parameter is the Asset function
- // Forth parameter is the AssetNames function.
- //
- // Returns the GET *Route.
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-files-into-app
- StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route
- // StaticEmbeddedGzip registers a route which can serve embedded gziped files
- // that are embedded using the https://github.com/kataras/bindata tool and only.
- // It's 8 times faster than the `StaticEmbeddedHandler` with `go-bindata` but
- // it sends gzip response only, so the client must be aware that is expecting a gzip body
- // (browsers and most modern browsers do that, so you can use it without fair).
- //
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-gziped-files-into-app
- StaticEmbeddedGzip(requestPath string, vdir string, gzipAssetFn func(name string) ([]byte, error), gzipNamesFn func() []string) *Route
// Favicon serves static favicon
// accepts 2 parameters, second is optional
// favPath (string), declare the system directory path of the __.ico
@@ -234,24 +217,6 @@ type Party interface {
//
// Returns the GET *Route.
Favicon(favPath string, requestPath ...string) *Route
- // StaticWeb returns a handler that serves HTTP requests
- // with the contents of the file system rooted at directory.
- //
- // first parameter: the route path
- // second parameter: the system directory
- //
- // for more options look router.StaticHandler.
- //
- // router.StaticWeb("/static", "./static")
- //
- // As a special case, the returned file server redirects any request
- // ending in "/index.html" to the same path, without the final
- // "index.html".
- //
- // StaticWeb calls the `StripPrefix(fullpath, NewStaticHandlerBuilder(systemPath).Listing(false).Build())`.
- //
- // Returns the GET *Route.
- StaticWeb(requestPath string, systemPath string) *Route
// Layout overrides the parent template layout with a more specific layout for this Party.
// It returns the current Party.
diff --git a/core/router/path.go b/core/router/path.go
index 0a8b4014..8df69db8 100644
--- a/core/router/path.go
+++ b/core/router/path.go
@@ -25,6 +25,15 @@ func WildcardParam(name string) string {
return prefix(name, WildcardParamStart)
}
+// WildcardFileParam wraps a named parameter "file" with the trailing "path" macro parameter type.
+// At build state this "file" parameter is prefixed with the request handler's `WildcardParamStart`.
+// Created mostly for routes that serve static files to be visibly collected by
+// the `Application#GetRouteReadOnly` via the `Route.Tmpl().Src` instead of
+// the underline request handler's representation (`Route.Path()`).
+func WildcardFileParam() string {
+ return "{file:path}"
+}
+
func convertMacroTmplToNodePath(tmpl macro.Template) string {
routePath := tmpl.Src
if len(routePath) > 1 && routePath[len(routePath)-1] == '/' {
diff --git a/core/router/route.go b/core/router/route.go
index 56fe444d..a7f53a09 100644
--- a/core/router/route.go
+++ b/core/router/route.go
@@ -29,25 +29,19 @@ type Route struct {
// Execution happens after Begin and main Handler(s), can be empty.
doneHandlers context.Handlers
- Path string `json:"path"` // "/api/user/{id:uint64}"
+ Path string `json:"path"` // the underline router's representation, i.e "/api/user/:id"
// FormattedPath all dynamic named parameters (if any) replaced with %v,
// used by Application to validate param values of a Route based on its name.
FormattedPath string `json:"formattedPath"`
- // StaticTarget if not empty, refers to the system (or virtual if embedded) directory
- // that this route is serving static files/resources from
- // or to a single static filename if this route created via `APIBuilder#Favicon`
- // or to a `StaticContentTarget` type if this rotue created by `APIBuilder#StaticContent`.
- //
- // If a route is serving static files via `APIBuilder`
- // there are two routes with the same dir/filename set to this field,
- // one for "HEAD" and the other for the "GET" http method.
- StaticTarget string
+ // StaticSites if not empty, refers to the system (or virtual if embedded) directory
+ // and sub directories that this "GET" route was registered to serve files and folders
+ // that contain index.html (a site). The index handler may registered by other
+ // route, manually or automatic by the framework,
+ // get the route by `Application#GetRouteByPath(staticSite.RequestPath)`.
+ StaticSites []context.StaticSite `json:"staticSites"`
}
-// StaticContentTarget used whenever a `Route#StaticTarget` refers to a raw []byte static content instead of a directory or a file.
-const StaticContentTarget = "content"
-
// NewRoute returns a new route based on its method,
// subdomain, the path (unparsed or original),
// handlers and the macro container which all routes should share.
@@ -69,7 +63,7 @@ func NewRoute(method, subdomain, unparsedPath, mainHandlerName string,
handlers = append(context.Handlers{macroEvaluatorHandler}, handlers...)
}
- path = cleanPath(path) // maybe unnecessary here but who cares in this moment
+ path = cleanPath(path) // maybe unnecessary here.
defaultName := method + subdomain + tmpl.Src
formattedPath := formatPath(path)
@@ -87,26 +81,26 @@ func NewRoute(method, subdomain, unparsedPath, mainHandlerName string,
return route, nil
}
-// use adds explicit begin handlers(middleware) to this route,
-// It's being called internally, it's useless for outsiders
-// because `Handlers` field is exported.
-// The callers of this function are: `APIBuilder#UseGlobal` and `APIBuilder#Done`.
+// Use adds explicit begin handlers to this route.
+// Alternatively the end-dev can prepend to the `Handlers` field.
+// Should be used before the `BuildHandlers` which is
+// called by the framework itself on `Application#Run` (build state).
//
-// BuildHandlers should be called to build the route's `Handlers`.
-func (r *Route) use(handlers context.Handlers) {
+// Used internally at `APIBuilder#UseGlobal` -> `beginGlobalHandlers` -> `APIBuilder#Handle`.
+func (r *Route) Use(handlers ...context.Handler) {
if len(handlers) == 0 {
return
}
r.beginHandlers = append(r.beginHandlers, handlers...)
}
-// use adds explicit done handlers to this route.
-// It's being called internally, it's useless for outsiders
-// because `Handlers` field is exported.
-// The callers of this function are: `APIBuilder#UseGlobal` and `APIBuilder#Done`.
+// Done adds explicit finish handlers to this route.
+// Alternatively the end-dev can append to the `Handlers` field.
+// Should be used before the `BuildHandlers` which is
+// called by the framework itself on `Application#Run` (build state).
//
-// BuildHandlers should be called to build the route's `Handlers`.
-func (r *Route) done(handlers context.Handlers) {
+// Used internally at `APIBuilder#DoneGlobal` -> `doneGlobalHandlers` -> `APIBuilder#Handle`.
+func (r *Route) Done(handlers ...context.Handler) {
if len(handlers) == 0 {
return
}
@@ -161,6 +155,13 @@ func (r Route) String() string {
r.Method, r.Subdomain, r.Tmpl().Src)
}
+// Equal compares the method, subdomaind and the
+// underline representation of the route's path,
+// instead of the `String` function which returns the front representation.
+func (r *Route) Equal(other *Route) bool {
+ return r.Method == other.Method && r.Subdomain == other.Subdomain && r.Path == other.Path
+}
+
// Tmpl returns the path template,
// it contains the parsed template
// for the route's path.
@@ -235,12 +236,12 @@ func (r Route) StaticPath() string {
if bidx == -1 || len(src) <= bidx {
return src // no dynamic part found
}
- if bidx == 0 { // found at first index,
- // but never happens because of the prepended slash
+ if bidx <= 1 { // found at first{...} or second index (/{...}),
+ // although first index should never happen because of the prepended slash.
return "/"
}
- return src[:bidx]
+ return src[:bidx-1] // (/static/{...} -> /static)
}
// ResolvePath returns the formatted path's %v replaced with the args.
@@ -272,10 +273,15 @@ func (r Route) Trace() string {
}
printfmt += fmt.Sprintf(" %s ", r.Tmpl().Src)
+ mainHandlerName := r.MainHandlerName
+ if !strings.HasSuffix(mainHandlerName, ")") {
+ mainHandlerName += "()"
+ }
+
if l := r.RegisteredHandlersLen(); l > 1 {
- printfmt += fmt.Sprintf("-> %s() and %d more", r.MainHandlerName, l-1)
+ printfmt += fmt.Sprintf("-> %s and %d more", mainHandlerName, l-1)
} else {
- printfmt += fmt.Sprintf("-> %s()", r.MainHandlerName)
+ printfmt += fmt.Sprintf("-> %s", mainHandlerName)
}
// printfmt := fmt.Sprintf("%s: %s >> %s", r.Method, r.Subdomain+r.Tmpl().Src, r.MainHandlerName)
@@ -316,3 +322,7 @@ func (rd routeReadOnlyWrapper) Tmpl() macro.Template {
func (rd routeReadOnlyWrapper) MainHandlerName() string {
return rd.Route.MainHandlerName
}
+
+func (rd routeReadOnlyWrapper) StaticSites() []context.StaticSite {
+ return rd.Route.StaticSites
+}
diff --git a/core/router/route_test.go b/core/router/route_test.go
new file mode 100644
index 00000000..58cc25f8
--- /dev/null
+++ b/core/router/route_test.go
@@ -0,0 +1,56 @@
+// white-box testing
+
+package router
+
+import (
+ "github.com/kataras/iris/macro"
+
+ "testing"
+)
+
+func TestRouteStaticPath(t *testing.T) {
+ var tests = []struct {
+ tmpl string
+ static string
+ }{
+ {
+ tmpl: "/files/{file:path}",
+ static: "/files",
+ },
+ {
+ tmpl: "/path",
+ static: "/path",
+ },
+ {
+ tmpl: "/path/segment",
+ static: "/path/segment",
+ },
+ {
+ tmpl: "/path/segment/{n:int}",
+ static: "/path/segment",
+ },
+ {
+ tmpl: "/path/{n:uint64}/{n:int}",
+ static: "/path",
+ },
+ {
+ tmpl: "/path/{n:uint64}/static",
+ static: "/path",
+ },
+ {
+ tmpl: "/{name}",
+ static: "/",
+ },
+ {
+ tmpl: "/",
+ static: "/",
+ },
+ }
+
+ for i, tt := range tests {
+ route := Route{tmpl: macro.Template{Src: tt.tmpl}}
+ if expected, got := tt.static, route.StaticPath(); expected != got {
+ t.Fatalf("[%d:%s] expected static path to be: '%s' but got: '%s'", i, tt.tmpl, expected, got)
+ }
+ }
+}
diff --git a/core/router/spa.go b/core/router/spa.go
deleted file mode 100644
index 0ff8ad5c..00000000
--- a/core/router/spa.go
+++ /dev/null
@@ -1,167 +0,0 @@
-package router
-
-import (
- "strings"
-
- "github.com/kataras/iris/context"
-)
-
-// AssetValidator returns true if "filename"
-// is asset, i.e: strings.Contains(filename, ".").
-type AssetValidator func(filename string) bool
-
-// SPABuilder helps building a single page application server
-// which serves both routes and files from the root path.
-type SPABuilder struct {
- // Root defaults to "/", it's the root path that explicitly set-ed,
- // this can be changed if more than SPAs are used on the same
- // iris router instance.
- Root string
- // emptyRoot can be changed with `ChangeRoot` only,
- // is, statically, true if root is empty
- // and if root is empty then let 404 fire from server-side anyways if
- // the passed `AssetHandler` returns 404 for a specific request path.
- // Defaults to false.
- emptyRoot bool
-
- IndexNames []string
- AssetHandler context.Handler
- AssetValidators []AssetValidator
-}
-
-// AddIndexName will add an index name.
-// If path == $filename then it redirects to Root, which defaults to "/".
-//
-// It can be called BEFORE the server start.
-func (s *SPABuilder) AddIndexName(filename string) *SPABuilder {
- s.IndexNames = append(s.IndexNames, filename)
- return s
-}
-
-// ChangeRoot modifies the `Root` request path that is
-// explicitly set-ed if the `AssetHandler` gave a Not Found (404)
-// previously, if request's path is the passed "path"
-// then it explicitly sets that and it retries executing the `AssetHandler`.
-//
-// Empty Root means that let 404 fire from server-side anyways.
-//
-// Change it ONLY if you use more than one typical SPAs on the same Iris Application instance.
-func (s *SPABuilder) ChangeRoot(path string) *SPABuilder {
- s.Root = path
- s.emptyRoot = path == ""
- return s
-}
-
-// NewSPABuilder returns a new Single Page Application builder
-// It does what StaticWeb or StaticEmbedded expected to do when serving files and routes at the same time
-// from the root "/" path.
-//
-// Accepts a static asset handler, which can be an app.StaticHandler, app.StaticEmbeddedHandler...
-func NewSPABuilder(assetHandler context.Handler) *SPABuilder {
- if assetHandler == nil {
- assetHandler = func(ctx context.Context) {
- ctx.Writef("empty asset handler")
- }
- }
-
- return &SPABuilder{
- Root: "/",
- IndexNames: nil,
- // "IndexNames" are empty by-default,
- // if the user wants to redirect to "/" from "/index.html" she/he can chage that to []string{"index.html"} manually
- // or use the `StaticHandler` as "AssetHandler" which does that already.
- AssetHandler: assetHandler,
- AssetValidators: []AssetValidator{
- func(path string) bool {
- return true // returns true by-default, if false then it fires 404.
- },
- },
- }
-}
-
-func (s *SPABuilder) isAsset(reqPath string) bool {
- for _, v := range s.AssetValidators {
- if !v(reqPath) {
- return false
- }
- }
- return true
-}
-
-// Handler serves the asset handler but in addition, it makes some checks before that,
-// based on the `AssetValidators` and `IndexNames`.
-func (s *SPABuilder) Handler(ctx context.Context) {
- path := ctx.Path()
-
- // make a validator call, by-default all paths are valid and this codeblock doesn't mean anything
- // but for cases that users wants to bypass an asset she/he can do that by modifiying the `APIBuilder#AssetValidators` field.
- //
- // It's here for backwards compatibility as well, see #803.
- if !s.isAsset(path) {
- // it's not asset, execute the registered route's handlers
- ctx.NotFound()
- return
- }
-
- for _, index := range s.IndexNames {
- if strings.HasSuffix(path, index) {
- if s.emptyRoot {
- ctx.NotFound()
- return
- }
- localRedirect(ctx, "."+s.Root)
- // s.Root should be manually registered to a route
- // (not always, only if custom handler used).
- // We don't setup an index handler here,
- // let full control to the developer via "AssetHandler"
- // (use of middleware, manually call of the ctx.ServeFile or ctx.View etc.)
- return
- }
- }
-
- s.AssetHandler(ctx)
-
- if context.StatusCodeNotSuccessful(ctx.GetStatusCode()) && !s.emptyRoot && path != s.Root {
- // If file was not something like a javascript file, or a css or anything that
- // the passed `AssetHandler` scan-ed then re-execute the `AssetHandler`
- // using the `Root` as the request path (virtually).
- //
- // If emptyRoot is true then
- // fire the response as it's, "AssetHandler" is fully responsible for it,
- // client-side's router for invalid paths will not work here else read below.
- //
- // Author's notes:
- // the server doesn't need to know all client routes,
- // client-side router is responsible for any kind of invalid paths,
- // so explicit set to root path.
- //
- // The most simple solution was to use a
- // func(ctx iris.Context) { ctx.ServeFile("$PATH/index.html") } as the "AssetHandler"
- // but many developers use the `StaticHandler` (as shown in the examples)
- // but it was not working as expected because it (correctly) fires
- // a 404 not found if a file based on the request path didn't found.
- //
- // We can't just do it before the "AssetHandler"'s execution
- // for two main reasons:
- // 1. if it's a file serve handler, like `StaticHandler` then it will never serve
- // the corresponding files!
- // 2. it may manually handle those things,
- // don't forget that "AssetHandler" can be
- // ANY iris handler, so we can't be sure what the developer may want to do there.
- //
- // "AssetHandler" as the "StaticHandler" a retry doesn't hurt,
- // it will give us a 404 if the file didn't found very fast WITHOUT moving to the
- // rest of its validation and serving implementation.
- //
- // Another idea would be to modify the "AssetHandler" on every `ChangeRoot`
- // call, which may give us some performance (ns) benefits
- // but this could be bad if root is set-ed before the "AssetHandler",
- // so keep it as it's.
- rootURL, err := ctx.Request().URL.Parse(s.Root)
- if err == nil {
- ctx.Request().URL = rootURL
- s.AssetHandler(ctx)
- }
-
- }
-}
diff --git a/doc.go b/doc.go
index be9ff2a6..cdc73cfe 100644
--- a/doc.go
+++ b/doc.go
@@ -918,66 +918,19 @@ Run
Static Files
- // StaticServe serves a directory as web resource
- // it's the simpliest form of the Static* functions
- // Almost same usage as StaticWeb
- // accepts only one required parameter which is the systemPath,
- // the same path will be used to register the GET and HEAD method routes.
- // If second parameter is empty, otherwise the requestPath is the second parameter
- // it uses gzip compression (compression on each request, no file cache).
+ // HandleDir registers a handler that serves HTTP requests
+ // with the contents of a file system (physical or embedded).
+ //
+ // first parameter : the route path
+ // second parameter : the system or the embedded directory that needs to be served
+ // third parameter : not required, the directory options, set fields is optional.
+ //
+ // for more options look router.FileServer.
+ //
+ // api.HandleDir("/static", "./assets", DirOptions {ShowList: true, Gzip: true, IndexName: "index.html"})
//
// Returns the GET *Route.
- StaticServe(systemPath string, requestPath ...string) (*Route, error)
-
- // StaticContent registers a GET and HEAD method routes to the requestPath
- // that are ready to serve raw static bytes, memory cached.
- //
- // Returns the GET *Route.
- StaticContent(reqPath string, cType string, content []byte) (*Route, error)
-
- // StaticEmbedded used when files are distributed inside the app executable, using go-bindata mostly
- // First parameter is the request path, the path which the files in the vdir will be served to, for example "/static"
- // Second parameter is the (virtual) directory path, for example "./assets"
- // Third parameter is the Asset function
- // Forth parameter is the AssetNames function.
- //
- // Returns the GET *Route.
- //
- // Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-files-into-app
- StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) (*Route, error)
-
- // Favicon serves static favicon
- // accepts 2 parameters, second is optional
- // favPath (string), declare the system directory path of the __.ico
- // requestPath (string), it's the route's path, by default this is the "/favicon.ico" because some browsers tries to get this by default first,
- // you can declare your own path if you have more than one favicon (desktop, mobile and so on)
- //
- // this func will add a route for you which will static serve the /yuorpath/yourfile.ico to the /yourfile.ico
- // (nothing special that you can't handle by yourself).
- // Note that you have to call it on every favicon you have to serve automatically (desktop, mobile and so on).
- //
- // Returns the GET *Route.
- Favicon(favPath string, requestPath ...string) (*Route, error)
-
- // StaticWeb returns a handler that serves HTTP requests
- // with the contents of the file system rooted at directory.
- //
- // first parameter: the route path
- // second parameter: the system directory
- // third OPTIONAL parameter: the exception routes
- // (= give priority to these routes instead of the static handler)
- // for more options look app.StaticHandler.
- //
- // app.StaticWeb("/static", "./static")
- //
- // As a special case, the returned file server redirects any request
- // ending in "/index.html" to the same path, without the final
- // "index.html".
- //
- // StaticWeb calls the StaticHandler(systemPath, listingDirectories: false, gzip: false ).
- //
- // Returns the GET *Route.
- StaticWeb(requestPath string, systemPath string, exceptRoutes ...*Route) (*Route, error)
+ HandleDir(requestPath, directory string, opts ...DirOptions) (getRoute *Route)
Example code:
@@ -990,19 +943,40 @@ Example code:
func main() {
app := iris.New()
- // This will serve the ./static/favicons/ion_32_32.ico to: localhost:8080/favicon.ico
- app.Favicon("./static/favicons/ion_32_32.ico")
+ app.Favicon("./assets/favicon.ico")
- // app.Favicon("./static/favicons/ion_32_32.ico", "/favicon_48_48.ico")
- // This will serve the ./static/favicons/ion_32_32.ico to: localhost:8080/favicon_48_48.ico
+ // first parameter is the request path
+ // second is the system directory
+ //
+ // app.HandleDir("/css", "./assets/css")
+ // app.HandleDir("/js", "./assets/js")
- app.Get("/", func(ctx iris.Context) {
- ctx.HTML(` press here to see the favicon.ico.
- At some browsers like chrome, it should be visible at the top-left side of the browser's window,
- because some browsers make requests to the /favicon.ico automatically,
- so iris serves your favicon in that path too (you can change it).`)
- }) // if favicon doesn't show to you, try to clear your browser's cache.
+ app.HandleDir("/static", "./assets", iris.DirOptions{
+ // Defaults to "/index.html", if request path is ending with IndexName
+ // then it redirects to ../ which another handler is handling it,
+ // that another handler, called index handler, is auto-registered by the framework
+ // if end developer does not managed to handle it by hand.
+ IndexName: "/index.html",
+ // When files should served under compression.
+ Gzip: false,
+ // List the files inside the current requested directory if `IndexName` not found.
+ ShowList: false,
+ // If `ShowList` is true then this function will be used instead of the default one to show the list of files of a current requested directory(dir).
+ // DirList: func(ctx context.Context, dirName string, dir http.File) error { ... }
+ //
+ // Optional validator that loops through each requested resource.
+ // AssetValidator: func(ctx iris.Context, name string) bool { ... }
+ })
+ // You can also register any index handler manually, order of registration does not matter:
+ // app.Get("/static", [...custom middleware...], func(ctx iris.Context) {
+ // [...custom code...]
+ // ctx.ServeFile("./assets/index.html", false)
+ // })
+ // http://localhost:8080/static
+ // http://localhost:8080/static/css/main.css
+ // http://localhost:8080/static/js/jquery-2.1.1.js
+ // http://localhost:8080/static/favicon.ico
app.Run(iris.Addr(":8080"))
}
diff --git a/go19.go b/go19.go
index aadcb525..5be3dafc 100644
--- a/go19.go
+++ b/go19.go
@@ -62,8 +62,8 @@ type (
// Look the `core/router#APIBuilder` for its implementation.
//
// A shortcut for the `core/router#Party`, useful when `PartyFunc` is being used.
- Party = router.Party
-
+ Party = router.Party
+ DirOptions = router.DirOptions
// ExecutionRules gives control to the execution of the route handlers outside of the handlers themselves.
// Usage:
// Party#SetExecutionRules(ExecutionRules {
diff --git a/httptest/httptest.go b/httptest/httptest.go
index 243f765b..0994aa02 100644
--- a/httptest/httptest.go
+++ b/httptest/httptest.go
@@ -89,7 +89,7 @@ func New(t *testing.T, app *iris.Application, setters ...OptionSetter) *httpexpe
app.Logger().SetLevel(conf.LogLevel)
if err := app.Build(); err != nil {
- if conf.Debug && (conf.LogLevel == "disable" || conf.LogLevel == "disabled") {
+ if conf.LogLevel != "disable" && conf.LogLevel != "disabled" {
app.Logger().Println(err.Error())
return nil
}
diff --git a/iris.go b/iris.go
index dd788361..1e1bfa87 100644
--- a/iris.go
+++ b/iris.go
@@ -359,12 +359,8 @@ var (
//
// A shortcut for the `context#NewConditionalHandler`.
NewConditionalHandler = context.NewConditionalHandler
- // StaticEmbeddedHandler returns a Handler which can serve
- // embedded into executable files.
- //
- //
- // Examples: https://github.com/kataras/iris/tree/master/_examples/file-server
- StaticEmbeddedHandler = router.StaticEmbeddedHandler
+
+ FileServer = router.FileServer
// StripPrefix returns a handler that serves HTTP requests
// by removing the given prefix from the request URL's Path
// and invoking the handler h. StripPrefix handles a
@@ -372,7 +368,7 @@ var (
// replying with an HTTP 404 not found error.
//
// Usage:
- // fileserver := Party#StaticHandler("./static_files", false, false)
+ // fileserver := iris.FileServer("./static_files", DirOptions {...})
// h := iris.StripPrefix("/static", fileserver)
// app.Get("/static/{f:path}", h)
// app.Head("/static/{f:path}", h)
@@ -437,7 +433,7 @@ var (
// Developers are free to extend this method's behavior
// by watching system directories changes manually and use of the `ctx.WriteWithExpiration`
// with a "modtime" based on the file modified date,
- // simillary to the `StaticWeb`(which sends status OK(200) and browser disk caching instead of 304).
+ // simillary to the `HandleDir`(which sends status OK(200) and browser disk caching instead of 304).
//
// A shortcut of the `cache#Cache304`.
Cache304 = cache.Cache304
@@ -489,19 +485,6 @@ var (
IsErrPath = context.IsErrPath
)
-// SPA accepts an "assetHandler" which can be the result of an
-// app.StaticHandler or app.StaticEmbeddedHandler.
-// Use that when you want to navigate from /index.html to / automatically
-// it's a helper function which just makes some checks based on the `IndexNames` and `AssetValidators`
-// before the assetHandler call.
-//
-// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/single-page-application
-func (app *Application) SPA(assetHandler context.Handler) *router.SPABuilder {
- s := router.NewSPABuilder(assetHandler)
- app.APIBuilder.HandleMany("GET HEAD", "/{f:path}", s.Handler)
- return s
-}
-
// ConfigureHost accepts one or more `host#Configuration`, these configurators functions
// can access the host created by `app.Run`,
// they're being executed when application is ready to being served to the public.
diff --git a/middleware/logger/config.go b/middleware/logger/config.go
index 991e57b4..3bb8974f 100644
--- a/middleware/logger/config.go
+++ b/middleware/logger/config.go
@@ -68,7 +68,10 @@ type Config struct {
// LogFunc is the writer which logs are written to,
// if missing the logger middleware uses the app.Logger().Infof instead.
// Note that message argument can be empty.
- LogFunc func(now time.Time, latency time.Duration, status, ip, method, path string, message interface{}, headerMessage interface{})
+ LogFunc func(endTime time.Time, latency time.Duration, status, ip, method, path string, message interface{}, headerMessage interface{})
+ // LogFuncCtx can be used instead of `LogFunc` if handlers need to customize the output based on
+ // custom request-time information that the LogFunc isn't aware of.
+ LogFuncCtx func(ctx context.Context, latency time.Duration)
// Skippers used to skip the logging i.e by `ctx.Path()` and serve
// the next/main handler immediately.
Skippers []SkipperFunc
@@ -83,15 +86,16 @@ type Config struct {
// LogFunc and Skippers to nil as well.
func DefaultConfig() Config {
return Config{
- Status: true,
- IP: true,
- Method: true,
- Path: true,
- Query: false,
- Columns: false,
- LogFunc: nil,
- Skippers: nil,
- skip: nil,
+ Status: true,
+ IP: true,
+ Method: true,
+ Path: true,
+ Query: false,
+ Columns: false,
+ LogFunc: nil,
+ LogFuncCtx: nil,
+ Skippers: nil,
+ skip: nil,
}
}
diff --git a/middleware/logger/logger.go b/middleware/logger/logger.go
index 98d5c8fc..324c92ad 100644
--- a/middleware/logger/logger.go
+++ b/middleware/logger/logger.go
@@ -100,6 +100,9 @@ func (l *requestLoggerMiddleware) ServeHTTP(ctx context.Context) {
if logFunc := l.config.LogFunc; logFunc != nil {
logFunc(endTime, latency, status, ip, method, path, message, headerMessage)
return
+ } else if logFuncCtx := l.config.LogFuncCtx; logFuncCtx != nil {
+ logFuncCtx(ctx, latency)
+ return
}
if l.config.Columns {
diff --git a/typescript/_examples/editor/main.go b/typescript/_examples/editor/main.go
index 59bfb4e4..f7c8023a 100644
--- a/typescript/_examples/editor/main.go
+++ b/typescript/_examples/editor/main.go
@@ -8,7 +8,7 @@ import (
func main() {
app := iris.New()
- app.StaticWeb("/scripts", "./www/scripts") // serve the scripts
+ app.HandleDir("/scripts", "./www/scripts") // serve the scripts
// when you edit a typescript file from the alm-tools
// it compiles it to javascript, have fun!
diff --git a/typescript/_examples/editor/www/scripts/app.js b/typescript/_examples/editor/www/scripts/app.js
new file mode 100644
index 00000000..263cbaff
--- /dev/null
+++ b/typescript/_examples/editor/www/scripts/app.js
@@ -0,0 +1,12 @@
+var User = (function () {
+ function User(fullname) {
+ this.name = fullname;
+ }
+ User.prototype.Hi = function (msg) {
+ return msg + " " + this.name;
+ };
+ return User;
+}());
+var user = new User("iris web framework!");
+var hi = user.Hi("Hello");
+window.alert(hi);
diff --git a/typescript/_examples/typescript/main.go b/typescript/_examples/typescript/main.go
index a42a2b92..27bf000c 100644
--- a/typescript/_examples/typescript/main.go
+++ b/typescript/_examples/typescript/main.go
@@ -14,7 +14,7 @@ import (
func main() {
app := iris.New()
- app.StaticWeb("/scripts", "./www") // serve the scripts
+ app.HandleDir("/scripts", "./www") // serve the scripts
app.Get("/", func(ctx iris.Context) {
ctx.ServeFile("./www/index.html", false)
diff --git a/view/django.go b/view/django.go
index d989a1a2..c5bd4295 100644
--- a/view/django.go
+++ b/view/django.go
@@ -11,24 +11,25 @@ import (
"strings"
"sync"
- "github.com/flosch/pongo2"
"github.com/kataras/iris/context"
+
+ "github.com/flosch/pongo2"
)
type (
- // Value conversion for pongo2.Value
- Value pongo2.Value
- // Error conversion for pongo2.Error
- Error pongo2.Error
- // FilterFunction conversion for pongo2.FilterFunction
- FilterFunction func(in *Value, param *Value) (out *Value, err *Error)
+ // Value type alias for pongo2.Value
+ Value = pongo2.Value
+ // Error type alias for pongo2.Error
+ Error = pongo2.Error
+ // FilterFunction type alias for pongo2.FilterFunction
+ FilterFunction = pongo2.FilterFunction
- // Parser conversion for pongo2.Parser
- Parser pongo2.Parser
- // Token conversion for pongo2.Token
- Token pongo2.Token
- // INodeTag conversion for pongo2.InodeTag
- INodeTag pongo2.INodeTag
+ // Parser type alias for pongo2.Parser
+ Parser = pongo2.Parser
+ // Token type alias for pongo2.Token
+ Token = pongo2.Token
+ // INodeTag type alias for pongo2.InodeTag
+ INodeTag = pongo2.INodeTag
// TagParser the function signature of the tag's parser you will have
// to implement in order to create a new tag.
//
@@ -41,44 +42,22 @@ type (
//
// Please see the Parser documentation on how to use the parser.
// See `RegisterTag` for more information about writing a tag as well.
- TagParser func(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error)
+ TagParser = pongo2.TagParser
)
-// AsValue converts any given value to a view.Value.
+// AsValue converts any given value to a pongo2.Value
// Usually being used within own functions passed to a template
// through a Context or within filter functions.
-func AsValue(i interface{}) *Value {
- return (*Value)(pongo2.AsValue(i))
-}
+//
+// Example:
+// AsValue("my string")
+//
+// Shortcut for `pongo2.AsValue`.
+var AsValue = pongo2.AsValue
// AsSafeValue works like AsValue, but does not apply the 'escape' filter.
-func AsSafeValue(i interface{}) *Value {
- return (*Value)(pongo2.AsSafeValue(i))
-}
-
-// GetValue returns the `Value` as *pongo2.Value type.
-// This method was added by balthild at https://github.com/kataras/iris/pull/765
-func (value *Value) GetValue() *pongo2.Value {
- return (*pongo2.Value)(value)
-}
-
-// GetError returns the `Error` as *pongo2.Error type.
-// This method was added by balthild at https://github.com/kataras/iris/pull/765
-func (error *Error) GetError() *pongo2.Error {
- return (*pongo2.Error)(error)
-}
-
-// GetParser returns the `Parser` as *pongo2.Parser type.
-// This method was added by balthild at https://github.com/kataras/iris/pull/765
-func (parser *Parser) GetParser() *pongo2.Parser {
- return (*pongo2.Parser)(parser)
-}
-
-// GetToken returns the `Token` as *pongo2.Token type.
-// This method was added by balthild at https://github.com/kataras/iris/pull/765
-func (token *Token) GetToken() *pongo2.Token {
- return (*pongo2.Token)(token)
-}
+// Shortcut for `pongo2.AsSafeValue`.
+var AsSafeValue = pongo2.AsSafeValue
type tDjangoAssetLoader struct {
baseDir string
@@ -200,13 +179,8 @@ func (s *DjangoEngine) RegisterFilter(filterName string, filterBody FilterFuncti
return s.registerFilter(filterName, filterBody)
}
-func (s *DjangoEngine) registerFilter(filterName string, filterBody FilterFunction) *DjangoEngine {
- fn := pongo2.FilterFunction(func(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
- theOut, theErr := filterBody((*Value)(in), (*Value)(param))
- return (*pongo2.Value)(theOut), (*pongo2.Error)(theErr)
- })
+func (s *DjangoEngine) registerFilter(filterName string, fn FilterFunction) *DjangoEngine {
pongo2.RegisterFilter(filterName, fn)
-
return s
}
@@ -216,12 +190,7 @@ func (s *DjangoEngine) registerFilter(filterName string, filterBody FilterFuncti
//
// See http://www.florian-schlachter.de/post/pongo2/ for more about
// writing filters and tags.
-func (s *DjangoEngine) RegisterTag(tagName string, parserFn TagParser) error {
- fn := func(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) {
- t, err := parserFn((*Parser)(doc), (*Token)(start), (*Parser)(arguments))
- return t, (*pongo2.Error)(err)
- }
-
+func (s *DjangoEngine) RegisterTag(tagName string, fn TagParser) error {
return pongo2.RegisterTag(tagName, fn)
}