serve webdav: add serve rc interface - fixes #4505

This commit is contained in:
Nick Craig-Wood 2025-03-28 18:09:03 +00:00
parent 780f4040ea
commit e37775bb41
2 changed files with 70 additions and 27 deletions

View File

@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"mime"
"net"
"net/http"
"os"
"path"
@ -21,8 +22,10 @@ import (
"github.com/rclone/rclone/cmd/serve/proxy"
"github.com/rclone/rclone/cmd/serve/proxy/proxyflags"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/config/flags"
"github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/fs/rc"
libhttp "github.com/rclone/rclone/lib/http"
"github.com/rclone/rclone/lib/http/serve"
"github.com/rclone/rclone/lib/systemd"
@ -70,6 +73,28 @@ func init() {
vfsflags.AddFlags(flagSet)
proxyflags.AddFlags(flagSet)
cmdserve.Command.AddCommand(Command)
cmdserve.AddRc("webdav", func(ctx context.Context, f fs.Fs, in rc.Params) (cmdserve.Handle, error) {
// Read VFS Opts
var vfsOpt = vfscommon.Opt // set default opts
err := configstruct.SetAny(in, &vfsOpt)
if err != nil {
return nil, err
}
// Read Proxy Opts
var proxyOpt = proxy.Opt // set default opts
err = configstruct.SetAny(in, &proxyOpt)
if err != nil {
return nil, err
}
// Read opts
var opt = Opt // set default opts
err = configstruct.SetAny(in, &opt)
if err != nil {
return nil, err
}
// Create server
return newWebDAV(ctx, f, &opt, &vfsOpt, &proxyOpt)
})
}
// Command definition for cobra
@ -145,17 +170,12 @@ done by the permissions on the socket.
cmd.CheckArgs(0, 0, command, args)
}
cmd.Run(false, false, command, func() error {
s, err := newWebDAV(context.Background(), f, &Opt)
if err != nil {
return err
}
err = s.serve()
s, err := newWebDAV(context.Background(), f, &Opt, &vfscommon.Opt, &proxy.Opt)
if err != nil {
return err
}
defer systemd.Notify()()
s.Wait()
return nil
return s.Serve()
})
return nil
},
@ -174,7 +194,7 @@ done by the permissions on the socket.
// might apply". In particular, whether or not renaming a file or directory
// overwriting another existing file or directory is an error is OS-dependent.
type WebDAV struct {
*libhttp.Server
server *libhttp.Server
opt Options
f fs.Fs
_vfs *vfs.VFS // don't use directly, use getVFS
@ -188,7 +208,7 @@ type WebDAV struct {
var _ webdav.FileSystem = (*WebDAV)(nil)
// Make a new WebDAV to serve the remote
func newWebDAV(ctx context.Context, f fs.Fs, opt *Options) (w *WebDAV, err error) {
func newWebDAV(ctx context.Context, f fs.Fs, opt *Options, vfsOpt *vfscommon.Options, proxyOpt *proxy.Options) (w *WebDAV, err error) {
w = &WebDAV{
f: f,
ctx: ctx,
@ -206,15 +226,15 @@ func newWebDAV(ctx context.Context, f fs.Fs, opt *Options) (w *WebDAV, err error
if w.etagHashType != hash.None {
fs.Debugf(f, "Using hash %v for ETag", w.etagHashType)
}
if proxy.Opt.AuthProxy != "" {
w.proxy = proxy.New(ctx, &proxy.Opt, &vfscommon.Opt)
if proxyOpt.AuthProxy != "" {
w.proxy = proxy.New(ctx, proxyOpt, vfsOpt)
// override auth
w.opt.Auth.CustomAuthFn = w.auth
} else {
w._vfs = vfs.New(f, &vfscommon.Opt)
w._vfs = vfs.New(f, vfsOpt)
}
w.Server, err = libhttp.NewServer(ctx,
w.server, err = libhttp.NewServer(ctx,
libhttp.WithConfig(w.opt.HTTP),
libhttp.WithAuth(w.opt.Auth),
libhttp.WithTemplate(w.opt.Template),
@ -234,7 +254,7 @@ func newWebDAV(ctx context.Context, f fs.Fs, opt *Options) (w *WebDAV, err error
}
w.webdavhandler = webdavHandler
router := w.Server.Router()
router := w.server.Router()
router.Use(
middleware.SetHeader("Accept-Ranges", "bytes"),
middleware.SetHeader("Server", "rclone/"+fs.Version),
@ -382,7 +402,7 @@ func (w *WebDAV) serveDir(rw http.ResponseWriter, r *http.Request, dirRemote str
}
// Make the entries for display
directory := serve.NewDirectory(dirRemote, w.Server.HTMLTemplate())
directory := serve.NewDirectory(dirRemote, w.server.HTMLTemplate())
for _, node := range dirEntries {
if vfscommon.Opt.NoModTime {
directory.AddHTMLEntry(node.Path(), node.IsDir(), node.Size(), time.Time{})
@ -398,15 +418,26 @@ func (w *WebDAV) serveDir(rw http.ResponseWriter, r *http.Request, dirRemote str
directory.Serve(rw, r)
}
// serve runs the http server in the background.
// Serve HTTP until the server is shutdown
//
// Use s.Close() and s.Wait() to shutdown server
func (w *WebDAV) serve() error {
w.Serve()
fs.Logf(w.f, "WebDav Server started on %s", w.URLs())
func (w *WebDAV) Serve() error {
w.server.Serve()
fs.Logf(w.f, "WebDav Server started on %s", w.server.URLs())
w.server.Wait()
return nil
}
// Addr returns the first address of the server
func (w *WebDAV) Addr() net.Addr {
return w.server.Addr()
}
// Shutdown the server
func (w *WebDAV) Shutdown() error {
return w.server.Shutdown()
}
// logRequest is called by the webdav module on every request
func (w *WebDAV) logRequest(r *http.Request, err error) {
fs.Infof(r.URL.Path, "%s from %s", r.Method, r.RemoteAddr)

View File

@ -18,11 +18,14 @@ import (
"time"
_ "github.com/rclone/rclone/backend/local"
"github.com/rclone/rclone/cmd/serve/proxy"
"github.com/rclone/rclone/cmd/serve/servetest"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/obscure"
"github.com/rclone/rclone/fs/filter"
"github.com/rclone/rclone/fs/rc"
"github.com/rclone/rclone/vfs/vfscommon"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/webdav"
@ -56,22 +59,23 @@ func TestWebDav(t *testing.T) {
opt.EtagHash = "MD5"
// Start the server
w, err := newWebDAV(context.Background(), f, &opt)
w, err := newWebDAV(context.Background(), f, &opt, &vfscommon.Opt, &proxy.Opt)
require.NoError(t, err)
require.NoError(t, w.serve())
go func() {
require.NoError(t, w.Serve())
}()
// Config for the backend we'll use to connect to the server
config := configmap.Simple{
"type": "webdav",
"vendor": "rclone",
"url": w.Server.URLs()[0],
"url": w.server.URLs()[0],
"user": testUser,
"pass": obscure.MustObscure(testPass),
}
return config, func() {
assert.NoError(t, w.Shutdown())
w.Wait()
}
}
@ -102,14 +106,15 @@ func TestHTTPFunction(t *testing.T) {
opt.Template.Path = testTemplate
// Start the server
w, err := newWebDAV(context.Background(), f, &opt)
w, err := newWebDAV(context.Background(), f, &opt, &vfscommon.Opt, &proxy.Opt)
assert.NoError(t, err)
require.NoError(t, w.serve())
go func() {
require.NoError(t, w.Serve())
}()
defer func() {
assert.NoError(t, w.Shutdown())
w.Wait()
}()
testURL := w.Server.URLs()[0]
testURL := w.server.URLs()[0]
pause := time.Millisecond
i := 0
for ; i < 10; i++ {
@ -259,3 +264,10 @@ func HelpTestGET(t *testing.T, testURL string) {
checkGolden(t, test.Golden, body)
}
}
func TestRc(t *testing.T) {
servetest.TestRc(t, rc.Params{
"type": "webdav",
"vfs_cache_mode": "off",
})
}