added cache only access server
This commit is contained in:
parent
2a0801b015
commit
b5ae5504a7
6 changed files with 217 additions and 8 deletions
|
|
@ -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("a, "quota", 1, "max disk usage quota in GiB")
|
flag.Int64Var("a, "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)
|
||||||
|
|
|
||||||
41
pkg/fs/fs.go
41
pkg/fs/fs.go
|
|
@ -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
102
pkg/srv/cache.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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("&"))
|
buf = bytes.ReplaceAll(buf, []byte{'&'}, []byte("&"))
|
||||||
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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue