From 764a74e2ecd66ce8a34f8321dc40ebffabc58e0d Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin Date: Sun, 17 Jan 2016 23:12:38 +0200 Subject: [PATCH] Added ServeFile and ServeFileUncompressed to be on par with net/http --- README.md | 2 +- fs.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++++----- fs_test.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++++ server.go | 2 ++ 4 files changed, 119 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 652ce8a..94ade03 100644 --- a/README.md +++ b/README.md @@ -308,7 +308,7 @@ fastttp.ListenAndServe(":80", m) * http.Error() -> [ctx.Error()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Error) * http.FileServer() -> [fasthttp.FSHandler()](https://godoc.org/github.com/valyala/fasthttp#FSHandler), [fasthttp.FS](https://godoc.org/github.com/valyala/fasthttp#FS) - * http.ServeFile() -> [ctx.SendFile()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SendFile) + * http.ServeFile() -> [ctx.ServeFile()](https://godoc.org/github.com/valyala/fasthttp#ServeFile) * http.Redirect() -> [ctx.Redirect()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Redirect) * http.NotFound() -> [ctx.NotFound()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.NotFound) * http.StripPrefix() -> [fasthttp.PathRewriteFunc](https://godoc.org/github.com/valyala/fasthttp#PathRewriteFunc) diff --git a/fs.go b/fs.go index d491966..bf58ec8 100644 --- a/fs.go +++ b/fs.go @@ -18,6 +18,59 @@ import ( "time" ) +// ServeFileUncompressed returns HTTP response containing file contents +// from the given path. +// +// Directory contents is returned if path points to directory. +// +// ServeFile may be used for saving network traffic when serving files +// with good compression ratio. +// +// See also RequestCtx.SendFile. +func ServeFileUncompressed(ctx *RequestCtx, path string) { + ctx.Request.Header.DelBytes(strAcceptEncoding) + ServeFile(ctx, path) +} + +// ServeFile returns HTTP response containing compressed file contents +// from the given path. +// +// HTTP response may contain uncompressed file contents in the following cases: +// +// * Missing 'Accept-Encoding: gzip' request header. +// * No write access to directory containing the file. +// +// Directory contents is returned if path points to directory. +// +// Use ServeFileUncompressed is you don't need serving compressed file contents. +// +// See also RequestCtx.SendFile. +func ServeFile(ctx *RequestCtx, path string) { + rootFSOnce.Do(func() { + rootFSHandler = rootFS.NewRequestHandler() + }) + if len(path) == 0 || path[0] != '/' { + // extend relative path to absolute path + var err error + if path, err = filepath.Abs(path); err != nil { + ctx.Logger().Printf("cannot resolve path %q to absolute file path: %s", path, err) + } + } + ctx.Request.SetRequestURI(path) + rootFSHandler(ctx) +} + +var ( + rootFSOnce sync.Once + rootFS = &FS{ + Root: "/", + GenerateIndexPages: true, + Compress: true, + AcceptByteRange: true, + } + rootFSHandler RequestHandler +) + // PathRewriteFunc must return new request path based on arbitrary ctx // info such as ctx.Path(). // @@ -178,16 +231,16 @@ func (fs *FS) NewRequestHandler() RequestHandler { root := fs.Root - // strip trailing slashes from the root path - for len(root) > 0 && root[len(root)-1] == '/' { - root = root[:len(root)-1] - } - // serve files from the current working directory if root is empty if len(root) == 0 { root = "." } + // strip trailing slashes from the root path + for len(root) > 0 && root[len(root)-1] == '/' { + root = root[:len(root)-1] + } + cacheDuration := fs.CacheDuration if cacheDuration <= 0 { cacheDuration = FSHandlerCacheDuration diff --git a/fs_test.go b/fs_test.go index ea666ca..f2020e7 100644 --- a/fs_test.go +++ b/fs_test.go @@ -12,6 +12,64 @@ import ( "time" ) +func TestFSServeFileCompressed(t *testing.T) { + var ctx RequestCtx + var req Request + req.SetRequestURI("http://foobar.com/baz") + req.Header.Set("Accept-Encoding", "gzip") + ctx.Init(&req, nil, nil) + + ServeFile(&ctx, "fs.go") + + var resp Response + s := ctx.Response.String() + br := bufio.NewReader(bytes.NewBufferString(s)) + if err := resp.Read(br); err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if string(resp.Header.Peek("Content-Encoding")) != "gzip" { + t.Fatalf("Unexpected 'Content-Encoding' %q. Expecting %q", resp.Header.Peek("Content-Encoding"), "gzip") + } + + body, err := resp.BodyGunzip() + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + expectedBody, err := getFileContents("/fs.go") + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if !bytes.Equal(body, expectedBody) { + t.Fatalf("unexpected body %q. expecting %q", body, expectedBody) + } +} + +func TestFSServeFileUncompressed(t *testing.T) { + var ctx RequestCtx + var req Request + req.SetRequestURI("http://foobar.com/baz") + ctx.Init(&req, nil, nil) + + ServeFileUncompressed(&ctx, "fs.go") + + var resp Response + s := ctx.Response.String() + br := bufio.NewReader(bytes.NewBufferString(s)) + if err := resp.Read(br); err != nil { + t.Fatalf("unexpected error: %s", err) + } + + body := resp.Body() + expectedBody, err := getFileContents("/fs.go") + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if !bytes.Equal(body, expectedBody) { + t.Fatalf("unexpected body %q. expecting %q", body, expectedBody) + } +} + func TestFSByteRangeConcurrent(t *testing.T) { fs := &FS{ Root: ".", diff --git a/server.go b/server.go index 268514b..9f8c44e 100644 --- a/server.go +++ b/server.go @@ -810,6 +810,8 @@ func (ctx *RequestCtx) ResetBody() { // Note that SendFile doesn't set Content-Type for the response body, // so set it yourself with SetContentType() before returning // from RequestHandler. +// +// See also ServeFile, FSHandler and FS. func (ctx *RequestCtx) SendFile(path string) error { ifModStr := ctx.Request.Header.peek(strIfModifiedSince) if len(ifModStr) > 0 {