Golang 实现简单WebDAV系统

当前有一台Windows Server的文件服务器,为了方便文件的存储和读取,不想使用网页(云盘)的形式发布到公网,于是想到能否用WebDav的方式来挂载硬盘在本地电脑的文件管理器中。

接下来就是折腾过程的记录

WebDAV (Web-based Distributed Authoring and Versioning) 一种基于HTTP1.1协议的通信协议。它扩展了HTTP 1.1,在GET、POST、HEAD等几个HTTP标准方法以外添加了一些新的方法,使应用程序可对Web Server直接读写,并支持写文件锁定(Locking)及解锁(Unlock),还可以支持文件的版本控制。

尝试PHP

由于WebDAV就是一个Web Sever,于是首先想到的就是用PHP,也是由于用得顺手。随之而来的就是服务器使用Apache还是Nginx?最终选择最简答的PHPStudy懒人包,集成PHP所需要的环境。

使用PHP的一个缺点就是运行环境复杂,需要控制的点太多。Windows Firewall尝试了很多次配置,都还是无法公网访问网页。于是逐渐放弃...

初见Caddy

紧接着开始了搜索简便搭建WebDAV的方式,一个标题 “用Caddy搭建WebDAV服务器” 吸引住我了,一看只需几行简简单单的配置就能开启一个WebDav服务器,于是马上上手。

Action speak louder than words!

在Caddy官网下载了运行文件,按照文中的配置,信心满满敲下命令,报错了!!原来Caddy已经升级到v2版本了,而网上找的文章基本全是v1版本的教程。于是只能一步步慢慢读官方 Document,终于将配置文件修改好了。

Caddy 2 is a powerful, enterprise-ready, open source web server with automatic HTTPS written in Go

在v1中WebDAV是一个内置的包,而在v2中需要在下载是勾选编译,得到的可运行文件才是包含WebDAV的,且v2的WebDAV插件实现的功能更少了,也不是和v1一样的维护者。对于authentication,在v2中已经使用哈希加密存储的方式了。

# First Caddyfile for Webdav
:8080 {
  encode gzip
  basicauth / {
        test XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  }
  webdav /{
  	root E:\caddy
  }
}

紧接着,测试时发现WebDAV正常运行,也可以连接,但是文件夹是锁定无法读写的。当时也不知道咋回事,看了源码也不懂于是陷入沉思。

Go实现WebDAV

突然发现源码中其实也是importgolang.org/x/net/webdav的库,为何不能直接用Go来实现呢?经过一番努力搜索,出来了个初版。

package main

import (
	"flag"
	"fmt"
	"golang.org/x/net/webdav"
	"net/http"
)

func main() {
	var addr *string
	var path *string
	addr = flag.String("addr", ":8080", "") // listen端口,默认8080
	path = flag.String("path", ".", "") // 文件路径,默认当前目录
	flag.Parse()
	fmt.Println("addr=", *addr, ", path=", *path) // 在控制台输出配置
	http.ListenAndServe(*addr, &webdav.Handler{
		FileSystem: webdav.Dir(*path),
		LockSystem: webdav.NewMemLS(),
	})
}

如此简陋,没有密码、没有运行错误判断、没有日志,But it works well! 因为没有接触过Go,完全不知道如何修改。又是一顿骚操作,密码验证的版本来了。

package main

import (
	"flag"
	"fmt"
	"golang.org/x/net/webdav"
	"net/http"
)

func main() {
	var addr *string
	var path *string
	//
	addr = flag.String("addr", ":8080", "")
	path = flag.String("path", ".", "")
	flag.Parse()

    fs := &webdav.Handler{
		FileSystem: webdav.Dir(*path),
		LockSystem: webdav.NewMemLS(),
	}
	http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		// 获取用户名/密码
		username, password, ok := req.BasicAuth()
		if !ok {
			w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
			w.WriteHeader(http.StatusUnauthorized)
			return
		}
		// 验证用户名/密码
		if username != "user" || password != "123456" {
			http.Error(w, "WebDAV: need authorized!", http.StatusUnauthorized)
			return
		}
		fs.ServeHTTP(w, req)
	})
	fmt.Println("addr=", *addr, ", path=", *path)
	http.ListenAndServe(*addr, nil)
}

但是如果这样的话,数据将是用HTTP在网络明文上传输,这和裸奔有什么区别???安全还是很重要的,遂有了HTTPS版本。

package main
import (
	"flag"
	"fmt"
	"net/http"
	"os"
	"golang.org/x/net/webdav"
)
var (
	flagRootDir   = flag.String("dir", "", "webdav root dir")
	flagHttpAddr  = flag.String("http", ":80", "http or https address")
	flagHttpsMode = flag.Bool("https-mode", false, "use https mode")
	flagCertFile  = flag.String("https-cert-file", "cert.pem", "https cert file")
	flagKeyFile   = flag.String("https-key-file", "key.pem", "https key file")
	flagUserName  = flag.String("user", "", "user name")
	flagPassword  = flag.String("password", "", "user password")
	flagReadonly  = flag.Bool("read-only", false, "read only")
)
func init() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of WebDAV Server\n")
		flag.PrintDefaults()
	}
}
func main() {
	flag.Parse()
	fs := &webdav.Handler{
		FileSystem: webdav.Dir(*flagRootDir),
		LockSystem: webdav.NewMemLS(),
	}
	http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		if *flagUserName != "" && *flagPassword != "" {
			username, password, ok := req.BasicAuth()
			if !ok {
				w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
				w.WriteHeader(http.StatusUnauthorized)
				return
			}
			if username != *flagUserName || password != *flagPassword {
				http.Error(w, "WebDAV: need authorized!", http.StatusUnauthorized)
				return
			}
		}
		if *flagReadonly {
			switch req.Method {
			case "PUT", "DELETE", "PROPPATCH", "MKCOL", "COPY", "MOVE":
				http.Error(w, "WebDAV: Read Only!!!", http.StatusForbidden)
				return
			}
		}
		fs.ServeHTTP(w, req)
	})
	if *flagHttpsMode {
		fmt.Println("HTTPS", "addr=", *flagHttpAddr, ", path=", *flagRootDir)
		http.ListenAndServeTLS(*flagHttpAddr, *flagCertFile, *flagKeyFile, nil)
	} else {
		fmt.Println("HTTP", "addr=", *flagHttpAddr, ", path=", *flagRootDir)
		http.ListenAndServe(*flagHttpAddr, nil)
	}
}

光有HTTPS还需要有证书,于是又用OpenSSL给服务器生成了一个自证书,但是这个问题就导致在Windows上直接连接时一直报错无法连接,由于不是可信任证书或者是无证书(证书生成和颁发这一块不是很清楚,后续学习补充);但是可以使用RaiDrive来正常挂载了

上一篇:学习WebDav


下一篇:nginx webdav模块实现http协议上传文件