当前有一台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
来正常挂载了