diff --git a/.gitignore b/.gitignore index dbec0c72..462f0d0e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ go.sum node_modules /_examples/issue-*/ /_examples/feature-*/ +_examples/**/uploads/* diff --git a/_examples/README.md b/_examples/README.md index d6833375..8831c8db 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -80,6 +80,7 @@ * [Recovery](recover/main.go) * [Profiling](pprof/main.go) * File Server + * [File Server](file-server/file-server/main.go) * [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) 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 57a88e8c..fd8116bb 100644 --- a/_examples/file-server/embedding-files-into-app/main_test.go +++ b/_examples/file-server/embedding-files-into-app/main_test.go @@ -78,10 +78,6 @@ func TestEmbeddingFilesIntoApp(t *testing.T) { 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/file-server/main.go b/_examples/file-server/file-server/main.go new file mode 100644 index 00000000..0ce54002 --- /dev/null +++ b/_examples/file-server/file-server/main.go @@ -0,0 +1,66 @@ +package main + +import ( + "crypto/md5" + "fmt" + "io" + "mime/multipart" + "os" + "strconv" + "strings" + "time" + + "github.com/kataras/iris/v12" +) + +func init() { + os.Mkdir("./uploads", 0700) +} + +func main() { + app := iris.New() + app.RegisterView(iris.HTML("./views", ".html")) + + app.Get("/", index) + + app.Get("/upload", uploadView) + app.Post("/upload", upload) + + app.HandleDir("/files", "./uploads", iris.DirOptions{ + Gzip: true, + ShowList: true, + }) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + ctx.Redirect("/upload") +} + +func uploadView(ctx iris.Context) { + now := time.Now().Unix() + h := md5.New() + io.WriteString(h, strconv.FormatInt(now, 10)) + token := fmt.Sprintf("%x", h.Sum(nil)) + + ctx.View("upload.html", token) +} + +func upload(ctx iris.Context) { + _, err := ctx.UploadFormFiles("./uploads", beforeSave) + if err != nil { + ctx.StopWithError(iris.StatusInternalServerError, err) + return + } + + ctx.Redirect("/files") +} + +func beforeSave(ctx iris.Context, file *multipart.FileHeader) { + ip := ctx.RemoteAddr() + ip = strings.ReplaceAll(ip, ".", "_") + ip = strings.ReplaceAll(ip, ":", "_") + + file.Filename = ip + "-" + file.Filename +} diff --git a/_examples/file-server/file-server/views/upload.html b/_examples/file-server/file-server/views/upload.html new file mode 100644 index 00000000..6954b82f --- /dev/null +++ b/_examples/file-server/file-server/views/upload.html @@ -0,0 +1,17 @@ + + + + + + Upload Files + + +
+ + + +
+ + \ No newline at end of file diff --git a/context/context.go b/context/context.go index cb4a0e97..3152cd7f 100644 --- a/context/context.go +++ b/context/context.go @@ -444,16 +444,18 @@ type Context interface { // Header adds a header to the response writer. Header(name string, value string) - // ContentType sets the response writer's header key "Content-Type" to the 'cType'. + // ContentType sets the response writer's + // header "Content-Type" to the 'cType'. ContentType(cType string) - // GetContentType returns the response writer's header value of "Content-Type" - // which may, set before with the 'ContentType'. + // GetContentType returns the response writer's + // header value of "Content-Type". GetContentType() string - // GetContentType returns the request's header value of "Content-Type". + // GetContentType returns the request's + // trim-ed(without the charset and priority values) + // header value of "Content-Type". GetContentTypeRequested() string - - // GetContentLength returns the request's header value of "Content-Length". - // Returns 0 if header was unable to be found or its value was not a valid number. + // GetContentLength returns the request's + // header value of "Content-Length". GetContentLength() int64 // StatusCode sets the status code header to the response. @@ -2256,7 +2258,8 @@ func (ctx *context) contentTypeOnce(cType string, charset string) { ctx.writer.Header().Set(ContentTypeHeaderKey, cType) } -// ContentType sets the response writer's header key "Content-Type" to the 'cType'. +// ContentType sets the response writer's +// header "Content-Type" to the 'cType'. func (ctx *context) ContentType(cType string) { if cType == "" { return @@ -2282,19 +2285,21 @@ func (ctx *context) ContentType(cType string) { ctx.writer.Header().Set(ContentTypeHeaderKey, cType) } -// GetContentType returns the response writer's header value of "Content-Type" -// which may, set before with the 'ContentType'. +// GetContentType returns the response writer's +// header value of "Content-Type". func (ctx *context) GetContentType() string { return ctx.writer.Header().Get(ContentTypeHeaderKey) } -// GetContentType returns the request's header value of "Content-Type". +// GetContentType returns the request's +// trim-ed(without the charset and priority values) +// header value of "Content-Type". func (ctx *context) GetContentTypeRequested() string { return TrimHeaderValue(ctx.GetHeader(ContentTypeHeaderKey)) } -// GetContentLength returns the request's header value of "Content-Length". -// Returns 0 if header was unable to be found or its value was not a valid number. +// GetContentLength returns the request's +// header value of "Content-Length". func (ctx *context) GetContentLength() int64 { if v := ctx.GetHeader(ContentLengthHeaderKey); v != "" { n, _ := strconv.ParseInt(v, 10, 64) @@ -3427,7 +3432,7 @@ func (ctx *context) TryWriteGzip(b []byte) (int, error) { n, err := ctx.WriteGzip(b) if err != nil { // check if the error came from gzip not allowed and not the writer itself - if errors.Is(err, ErrGzipNotSupported) { + if err == ErrGzipNotSupported { // client didn't supported gzip, write them uncompressed: return ctx.writer.Write(b) } @@ -4239,6 +4244,8 @@ type N struct { Other []byte // custom content types. } +var _ ContentSelector = N{} + // SelectContent returns a content based on the matched negotiated "mime". func (n N) SelectContent(mime string) interface{} { switch mime { @@ -4419,7 +4426,6 @@ func (ctx *context) Negotiate(v interface{}) (int, error) { ctx.StatusCode(http.StatusNotAcceptable) return -1, ErrContentNotSupported } - } } diff --git a/context/route.go b/context/route.go index dcce5f36..56104828 100644 --- a/context/route.go +++ b/context/route.go @@ -2,10 +2,6 @@ package context import ( "io" - "os" - "path" - "path/filepath" - "strings" "time" "github.com/kataras/iris/v12/macro" @@ -66,13 +62,6 @@ type RouteReadOnly interface { // MainHandlerIndex returns the first registered handler's index for the route. MainHandlerIndex() int - // 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 - // Sitemap properties: https://www.sitemaps.org/protocol.html // GetLastMod returns the date of last modification of the file served by this route. @@ -82,51 +71,3 @@ type RouteReadOnly interface { // GetPriority returns the priority of this route's URL relative to other URLs on your site. GetPriority() float32 } - -// 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/router/api_builder.go b/core/router/api_builder.go index 06cd4fb8..678eb062 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -390,12 +390,11 @@ func (api *APIBuilder) HandleMany(methodOrMulti string, relativePathorMulti stri // // api.HandleDir("/static", "./assets", DirOptions {ShowList: true, Gzip: true, IndexName: "index.html"}) // -// Returns the GET *Route. +// Returns all the registered routes, including GET index and path patterm and HEAD. // // 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) { +func (api *APIBuilder) HandleDir(requestPath, directory string, opts ...DirOptions) (routes []*Route) { options := getDirOptions(opts...) - h := FileServer(directory, options) description := directory fileName, lineNumber := context.HandlerFileLine(h) // take those before StripPrefix. @@ -408,42 +407,15 @@ func (api *APIBuilder) HandleDir(requestPath, directory string, opts ...DirOptio 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 - } - - if n := len(api.relativePath); n > 0 && api.relativePath[n-1] == SubdomainPrefix[0] { - // this api is a subdomain-based. - slashIdx := strings.IndexByte(s.RequestPath, '/') - if slashIdx == -1 { - slashIdx = 0 - } - - requestPath = s.RequestPath[slashIdx:] - } else { - requestPath = s.RequestPath[strings.Index(s.RequestPath, api.relativePath)+len(api.relativePath):] - } - - if requestPath == "" { - requestPath = "/" - } - - routes = append(routes, api.CreateRoutes([]string{http.MethodGet}, requestPath, h)...) - getRoute.StaticSites = append(getRoute.StaticSites, s) + if api.GetRouteByPath(fullpath) == nil { + // register index if not registered by the end-developer. + routes = api.CreateRoutes([]string{http.MethodGet, http.MethodHead}, requestPath, h) } + requestPath = joinPath(requestPath, WildcardFileParam()) + + routes = append(routes, api.CreateRoutes([]string{http.MethodGet, http.MethodHead}, requestPath, h)...) + for _, route := range routes { if route.Method == http.MethodHead { } else { @@ -457,7 +429,7 @@ func (api *APIBuilder) HandleDir(requestPath, directory string, opts ...DirOptio } } - return getRoute + return routes } // CreateRoutes returns a list of Party-based Routes. diff --git a/core/router/fs.go b/core/router/fs.go index c9010362..1fd0bd95 100644 --- a/core/router/fs.go +++ b/core/router/fs.go @@ -316,10 +316,22 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { if d.IsDir() { name += "/" } + + upath := "" + // if ctx.Path() == "/" && dirName == strings.TrimPrefix(directory, "./") { + if ctx.Path() == "/" { + upath = ctx.GetCurrentRoute().StaticPath() + "/" + name + } else { + upath = "./" + dirName + "/" + name + } + + url := url.URL{ + Path: upath, + } // edit here to redirect correctly, standard library misses that. + // 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. _, err = ctx.Writef("%s\n", url.String(), htmlReplacer.Replace(name)) if err != nil { return err diff --git a/core/router/party.go b/core/router/party.go index 8efb92df..73028d5e 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -152,14 +152,14 @@ type Party interface { // 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. + // Alternatively, to get just the handler for that look the FileServer function instead. // // api.HandleDir("/static", "./assets", DirOptions {ShowList: true, Gzip: true, IndexName: "index.html"}) // - // Returns the GET *Route. + // Returns all the registered routes, including GET index and path patterm and HEAD. // // Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server - HandleDir(requestPath, directory string, opts ...DirOptions) *Route + HandleDir(requestPath, directory string, opts ...DirOptions) []*Route // None registers an "offline" route // see context.ExecRoute(routeName) and diff --git a/core/router/route.go b/core/router/route.go index ac5dc9f2..6b64b1d5 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -51,13 +51,7 @@ type Route struct { RegisterFileName string `json:"registerFileName"` RegisterLineNumber int `json:"registerLineNumber"` - // 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"` - topLink *Route + topLink *Route // Sitemap properties: https://www.sitemaps.org/protocol.html LastMod time.Time `json:"lastMod,omitempty"` @@ -521,10 +515,6 @@ func (rd routeReadOnlyWrapper) MainHandlerIndex() int { return rd.Route.MainHandlerIndex } -func (rd routeReadOnlyWrapper) StaticSites() []context.StaticSite { - return rd.Route.StaticSites -} - func (rd routeReadOnlyWrapper) GetLastMod() time.Time { return rd.Route.LastMod } diff --git a/iris.go b/iris.go index 0b6a6e1e..e8aa12d9 100644 --- a/iris.go +++ b/iris.go @@ -212,6 +212,9 @@ func Default() *Application { app := New() app.Use(recover.New()) app.Use(requestLogger.New()) + app.Use(Gzip) + app.Use(GzipReader) + app.defaultMode = true return app @@ -615,6 +618,12 @@ var ( // // A shortcut for the `context#ErrPushNotSupported`. ErrPushNotSupported = context.ErrPushNotSupported + // ErrGzipNotSupported may be returned from + // `WriteGzip` and `GzipReader` methods if + // the client does not support the "gzip" compression. + // + // A shortcut for the `context#ErrGzipNotSupported`. + ErrGzipNotSupported = context.ErrGzipNotSupported ) // Constants for input argument at `router.RouteRegisterRule`.