added cache only access server

This commit is contained in:
ston1th 2022-03-22 21:48:59 +01:00
commit b5ae5504a7
6 changed files with 217 additions and 8 deletions

View file

@ -27,6 +27,7 @@ var (
metadata string metadata string
listenHttp string listenHttp string
listenWebdav string listenWebdav string
listenCache string
quota int64 quota int64
log logr.Logger log logr.Logger
@ -41,6 +42,7 @@ func main() {
flag.StringVar(&metadata, "data", "", "path to metadata file") flag.StringVar(&metadata, "data", "", "path to metadata file")
flag.StringVar(&listenHttp, "listen", "127.0.0.1:8080", "listen addr:port") flag.StringVar(&listenHttp, "listen", "127.0.0.1:8080", "listen addr:port")
flag.StringVar(&listenWebdav, "webdav", "", "listen addr:port for webdav") flag.StringVar(&listenWebdav, "webdav", "", "listen addr:port for webdav")
flag.StringVar(&listenCache, "cache", "", "listen addr:port for cache only")
flag.Int64Var(&quota, "quota", 1, "max disk usage quota in GiB") flag.Int64Var(&quota, "quota", 1, "max disk usage quota in GiB")
flag.Parse() flag.Parse()
@ -51,14 +53,23 @@ func main() {
if err != nil { if err != nil {
klog.Fatalf("init failed: %s", err) klog.Fatalf("init failed: %s", err)
} }
s := &http.Server{Addr: listenHttp, Handler: srv.NewFileServer(filesystem, klogr.New().WithName("server"))} s := &http.Server{
Addr: listenHttp,
Handler: srv.NewFileServer(
filesystem,
klogr.New().WithName("server"),
),
}
go func() { go func() {
err := s.ListenAndServe() err := s.ListenAndServe()
if err != nil && err != http.ErrServerClosed { if err != nil && err != http.ErrServerClosed {
klog.Fatalf("init failed: %s", err) klog.Fatalf("init failed: %s", err)
} }
}() }()
var dav *http.Server var (
dav *http.Server
cache *http.Server
)
if listenWebdav != "" { if listenWebdav != "" {
dav = &http.Server{ dav = &http.Server{
Addr: listenWebdav, Addr: listenWebdav,
@ -74,6 +85,21 @@ func main() {
} }
}() }()
} }
if listenCache != "" {
cache = &http.Server{
Addr: listenCache,
Handler: srv.NewCacheServer(
filesystem,
klogr.New().WithName("cache"),
),
}
go func() {
err := cache.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
klog.Fatalf("init failed: %s", err)
}
}()
}
sigs := make(chan os.Signal, 1) sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs <-sigs
@ -86,6 +112,11 @@ func main() {
dav.Shutdown(ctx) dav.Shutdown(ctx)
cancel() cancel()
} }
if cache != nil {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
cache.Shutdown(ctx)
cancel()
}
filesystem.Close() filesystem.Close()
log.Info("cachefs shutdown completed") log.Info("cachefs shutdown completed")
os.Exit(0) os.Exit(0)

View file

@ -128,11 +128,50 @@ func (fs *FS) RemoveDst(name string) error {
return os.Remove(dp) return os.Remove(dp)
} }
func (fs *FS) statDst(name string) (fi stdfs.FileInfo, err error) { func (fs *FS) StatDst(name string) (fi stdfs.FileInfo, err error) {
fi, err = fs.sc.Get(name)
if err == nil {
return
}
return fs.statDst(name)
}
func (fs *FS) statDst(name string) (stdfs.FileInfo, error) {
_, dp := fs.paths(name) _, dp := fs.paths(name)
return os.Stat(dp) return os.Stat(dp)
} }
func (fs *FS) OpenDst(name string) (f http.File, err error) {
log := fs.log.WithValues("file", name)
_, dp := fs.paths(name)
fi, err := fs.StatDst(name)
if err != nil {
if !skipLog(name) {
log.Error(err, "error stat cache file")
}
return
}
file, err := os.Open(dp)
if err != nil {
if !skipLog(name) {
log.Error(err, "error opening cache file")
}
return
}
if fi.IsDir() {
f = &Dir{log: log.WithName("dir"), f: file, dc: fs.dc}
return
}
md := fs.metadata(name, fi.Size())
f = &File{
log: log,
f: file,
md: md,
offline: true,
}
return
}
func (fs *FS) CancelPreload(name string) { func (fs *FS) CancelPreload(name string) {
if cancel, ok := fs.pm[name]; ok { if cancel, ok := fs.pm[name]; ok {
cancel() cancel()

102
pkg/srv/cache.go Normal file
View file

@ -0,0 +1,102 @@
// Copyright (C) 2022 Marius Schellenberger
package srv
import (
"io"
"net/http"
"path"
"strings"
"cachefs/pkg/fs"
"github.com/go-logr/logr"
)
type CacheFS struct {
fs *fs.FS
}
func (cfs *CacheFS) Open(name string) (http.File, error) {
return cfs.fs.OpenDst(name)
}
type CacheServer struct {
log logr.Logger
fs *fs.FS
h http.Handler
}
func NewCacheServer(fs *fs.FS, log logr.Logger) http.Handler {
return &CacheServer{log, fs, http.FileServer(&CacheFS{fs})}
}
func (cs *CacheServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s := &statusInterceptor{w: w}
w = s
if !skipLog(r.RequestURI) {
defer func() {
if s.Status() == http.StatusPartialContent {
return
}
cs.log.Info("access",
"client", r.RemoteAddr,
"method", r.Method,
"status", s.Status(),
"uri", r.RequestURI,
)
}()
}
upath := r.URL.Path
if !strings.HasPrefix(upath, "/") {
upath = "/" + upath
r.URL.Path = upath
}
p := path.Clean(upath)
option := r.FormValue("o")
d, err := cs.fs.StatDst(p)
if err != nil {
msg, code := toHTTPError(err)
http.Error(w, msg, code)
return
}
h := w.Header()
if option == "v" {
h.Set(csp, videoCSP)
err = video.Execute(w, r.URL.Path)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
if !d.IsDir() {
cs.h.ServeHTTP(w, r)
return
}
i := &responseInterceptor{w: w}
r.Header.Del("If-Modified-Since")
r.Header.Del("Cache-Control")
cs.h.ServeHTTP(i, r)
h.Del("Last-Modified")
paths, err := i.GetPaths(p, cs.fs, true)
if err == io.EOF {
w.WriteHeader(i.Status())
return
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
h.Set(csp, indexCSP)
w.WriteHeader(i.Status())
err = cache.Execute(w, paths)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

View file

@ -91,7 +91,7 @@ func (r *responseInterceptor) Status() int {
return r.status return r.status
} }
func (r *responseInterceptor) GetPaths(path string, fs *fs.FS) (dc dirContents, err error) { func (r *responseInterceptor) GetPaths(path string, fs *fs.FS, relative bool) (dc dirContents, err error) {
buf := r.buf.Bytes() buf := r.buf.Bytes()
buf = bytes.ReplaceAll(buf, []byte{'&'}, []byte("&#38;")) buf = bytes.ReplaceAll(buf, []byte{'&'}, []byte("&#38;"))
err = xml.Unmarshal(buf, &dc) err = xml.Unmarshal(buf, &dc)
@ -106,6 +106,9 @@ func (r *responseInterceptor) GetPaths(path string, fs *fs.FS) (dc dirContents,
name := template.HTML(p) name := template.HTML(p)
full := path + "/" + p full := path + "/" + p
uri := template.HTML(full) uri := template.HTML(full)
if relative {
uri = template.HTML(p)
}
if p[len(p)-1] == '/' { if p[len(p)-1] == '/' {
dc.Dirs = append(dc.Dirs, dir{ dc.Dirs = append(dc.Dirs, dir{
Name: name, Name: name,

View file

@ -103,7 +103,7 @@ func (fs *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.Del("Last-Modified") h.Del("Last-Modified")
paths, err := i.GetPaths(p, fs.fs) paths, err := i.GetPaths(p, fs.fs, false)
if err == io.EOF { if err == io.EOF {
w.WriteHeader(i.Status()) w.WriteHeader(i.Status())
return return

View file

@ -5,7 +5,7 @@ package srv
import "html/template" import "html/template"
var ( var (
index = template.Must(template.New("index").Parse(`<!doctype html> indexHead = `<!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@ -107,8 +107,9 @@ span {
font-size: 15px; font-size: 15px;
} }
</style> </style>
</head> </head>`
<body>
index = template.Must(template.New("index").Parse(indexHead + `<body>
<article> <article>
<pre>[v]: show video <pre>[v]: show video
[n]: skip file caching [n]: skip file caching
@ -143,6 +144,39 @@ span {
</table> </table>
</article> </article>
</body> </body>
</html>`))
cache = template.Must(template.New("cache").Parse(indexHead + `<body>
<article>
<pre>[v]: show video</pre>
<table>
<tr class="listitem">
<td class="listpath up"><a href="../">../</a></td>
<td class="listoptions">[dir]</td>
</tr>
<tr class="spacer"><td colspan="2"></td></tr>
{{range $s := .Dirs -}}
<tr class="listitem">
<td class="listpath"><a href="{{$s.URI}}">{{$s.Name}}</a></td>
<td class="listoptions">[dir]</td>
</tr>
<tr class="spacer"><td colspan="2"></td></tr>
{{end -}}
{{range $s := .Files -}}
<tr class="listitem">
<td class="listpath"><a id="{{$s.Anchor}}" href="{{$s.URI}}">{{$s.Name}}</a></td>
<td class="listoptions">
{{if ge $s.Status 0}}<span>{{$s.Status}}%</span>{{end}}<a href="{{$s.URI}}?o=v">[v]</a>
</td>
</tr>
<tr class="spacer"><td colspan="2">
{{if ge $s.Status 0}}
<div class="spacer {{if le $s.Status 35}}red{{else if le $s.Status 65}}yellow{{else}}green{{end}}" style="width: {{$s.Status}}%;"></div>
{{end -}}
</td></tr>
{{end -}}
</table>
</article>
</body>
</html>`)) </html>`))
video = template.Must(template.New("video").Parse(`<!doctype html> video = template.Must(template.New("video").Parse(`<!doctype html>
<html lang="en"> <html lang="en">