用SpringBoot做一个Windows可挂载到本地的NAS网盘

先看使用效果:   能在上面修改删除添加文件.

1. 准备工作

调研了一下,windows挂载的几种方式如下:

1. NFSv3(RFC1813)可以基于Netty做开发,需要基于RFC1813实现linux的网络协议服务器

2. FTP(以前实现过 Apache有开源的Jar内嵌了FTP服务)

3. WebDav(tomcat自带了WebDav的Servlet 基于源码改造改造即可使用)

2. 选型工作

因为NFSv3只有RFC文件,资料比较少如果要实现需要抓包看TCP和UDP,成本较大所以未采用。

FTP实现过,需要在项目多加端口,暂不考虑。

最后就选择了WebDav。

3. 搭建WebDav&问题

https://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/servlets/WebdavServlet.html
参考这个链接写了个SpringBoot版的Serverlet

@WebServlet(name = "MyServlet", urlPatterns = {UploadConstant.WEB_DAV_URL}
        , initParams = {@WebInitParam(name = "listings", value = "true"),
        @WebInitParam(name = "readonly", value = "false"),
        @WebInitParam(name = "debug", value = "0")
})
public class WebDavSupport extends WebdavServlet {

}

同时application启动类上加上   @ServletComponentScan

发现windows可以连接,但是每次重启后文件会消失,而且缺少我需要的密码还有自定义目录位置。

 因为SpringBoot下WebDav暴露的目录是一个临时目录: eg

 C:/Users/%username%/AppData/Local/Temp/tomcat-docbase.8080.9768959453169634688

 也就是所有上传的文件都到这个临时目录里面了。

4. 源码分析

逻辑基本在这两个类里面: WebdavServlet处理了大部分逻辑,少部分在DefaultServlet里面
org.apache.catalina.servlets.DefaultServlet                
                ^
                |
org.apache.catalina.servlets.WebdavServlet

  WebdavServlet 源码所有对资源的操作都是用了 DefaultServlet 的resources对象。

   例如copyResource

/**
     * Copy a resource.
     *
     * @param req Servlet request
     * @param resp Servlet response
     * @return boolean true if the copy is successful
     * @throws IOException If an IO error occurs
     */
    private boolean copyResource(HttpServletRequest req,
                                 HttpServletResponse resp)
            throws IOException {

        // Parsing destination header

        String destinationPath = req.getHeader("Destination");

        if (destinationPath == null) {
            resp.sendError(WebdavStatus.SC_BAD_REQUEST);
            return false;
        }

        // Remove url encoding from destination
        destinationPath = UDecoder.URLDecode(destinationPath, StandardCharsets.UTF_8);

        int protocolIndex = destinationPath.indexOf("://");
        if (protocolIndex >= 0) {
            // if the Destination URL contains the protocol, we can safely
            // trim everything upto the first "/" character after "://"
            int firstSeparator =
                destinationPath.indexOf('/', protocolIndex + 4);
            if (firstSeparator < 0) {
                destinationPath = "/";
            } else {
                destinationPath = destinationPath.substring(firstSeparator);
            }
        } else {
            String hostName = req.getServerName();
            if ((hostName != null) && (destinationPath.startsWith(hostName))) {
                destinationPath = destinationPath.substring(hostName.length());
            }

            int portIndex = destinationPath.indexOf(':');
            if (portIndex >= 0) {
                destinationPath = destinationPath.substring(portIndex);
            }

            if (destinationPath.startsWith(":")) {
                int firstSeparator = destinationPath.indexOf('/');
                if (firstSeparator < 0) {
                    destinationPath = "/";
                } else {
                    destinationPath =
                        destinationPath.substring(firstSeparator);
                }
            }
        }

        // Normalise destination path (remove '.' and '..')
        destinationPath = RequestUtil.normalize(destinationPath);

        String contextPath = req.getContextPath();
        if ((contextPath != null) &&
            (destinationPath.startsWith(contextPath))) {
            destinationPath = destinationPath.substring(contextPath.length());
        }

        String pathInfo = req.getPathInfo();
        if (pathInfo != null) {
            String servletPath = req.getServletPath();
            if ((servletPath != null) &&
                (destinationPath.startsWith(servletPath))) {
                destinationPath = destinationPath
                    .substring(servletPath.length());
            }
        }

        if (debug > 0) {
            log("Dest path :" + destinationPath);
        }

        // Check destination path to protect special subdirectories
        if (isSpecialPath(destinationPath)) {
            resp.sendError(WebdavStatus.SC_FORBIDDEN);
            return false;
        }

        String path = getRelativePath(req);

        if (destinationPath.equals(path)) {
            resp.sendError(WebdavStatus.SC_FORBIDDEN);
            return false;
        }

        // Parsing overwrite header

        boolean overwrite = true;
        String overwriteHeader = req.getHeader("Overwrite");

        if (overwriteHeader != null) {
            if (overwriteHeader.equalsIgnoreCase("T")) {
                overwrite = true;
            } else {
                overwrite = false;
            }
        }

        // Overwriting the destination

        WebResource destination = resources.getResource(destinationPath);

        if (overwrite) {
            // Delete destination resource, if it exists
            if (destination.exists()) {
                if (!deleteResource(destinationPath, req, resp, true)) {
                    return false;
                }
            } else {
                resp.setStatus(WebdavStatus.SC_CREATED);
            }
        } else {
            // If the destination exists, then it's a conflict
            if (destination.exists()) {
                resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
                return false;
            }
        }

        // Copying source to destination

        Hashtable<String,Integer> errorList = new Hashtable<>();

        boolean result = copyResource(errorList, path, destinationPath);

        if ((!result) || (!errorList.isEmpty())) {
            if (errorList.size() == 1) {
                resp.sendError(errorList.elements().nextElement().intValue());
            } else {
                sendReport(req, resp, errorList);
            }
            return false;
        }

        // Copy was successful
        if (destination.exists()) {
            resp.setStatus(WebdavStatus.SC_NO_CONTENT);
        } else {
            resp.setStatus(WebdavStatus.SC_CREATED);
        }

        // Removing any lock-null resource which would be present at
        // the destination path
        lockNullResources.remove(destinationPath);

        return true;
    }

 在DefaultServlet 看到其实WebResourceRoot拿到的是org.apache.catalina.resources

 可以往里面加我们自己要暴露的路径就好了。这个操作比较像是代码启动tomcat。

//
resources = (WebResourceRoot) getServletContext().getAttribute(Globals.RESOURCES_ATTR);
//Globals.RESOURCES_ATTR
public static final String RESOURCES_ATTR = "org.apache.catalina.resources";  

 5. 编码&Windows下的坑

   0. 启动类@SpringBootApplication 下加上@ServletComponentScan

   1. 我们需要在WebResourceRoot(这是Tomcat自带的一个类)下添加自定义的路径,这里默认是D盘。

    2. 同时添加Basic密码校验

package hex.wang.hexworker.upload;

import org.apache.catalina.Globals;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.servlets.WebdavServlet;
import org.apache.catalina.webresources.DirResourceSet;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;

/**
 * @Author hex.wang
 * @Class WebDavSupport
 * @Description
 * @Date 2022/1/5 14:45
 */
@WebServlet(name = "MyServlet", urlPatterns = {UploadConstant.WEB_DAV_URL}
        , initParams = {@WebInitParam(name = "listings", value = "true"),
        @WebInitParam(name = "readonly", value = "false"),
        @WebInitParam(name = "debug", value = "0")
})

public class WebDavSupport extends WebdavServlet {
    @Value("${config.baseDav:d:/}")
    private String baseDav;
    @Value("${config.user:user}")
    private String user;
    @Value("${config.password:password}")
    private String password;


    @Override
    public void init() throws ServletException {
        WebResourceRoot webResourceRoot = (WebResourceRoot) getServletContext().getAttribute(Globals.RESOURCES_ATTR);
        File additionWebInfClasses = new File(baseDav);
        webResourceRoot.addPreResources(new DirResourceSet(webResourceRoot, "/", additionWebInfClasses
                .getAbsolutePath(), "/"));
        super.init();
    }

    public boolean auth(ServletRequest req, ServletResponse res) {
        String authorization = ((HttpServletRequest) req).getHeader("Authorization");
        if (authorization != null) {
            String base64 = authorization.replaceFirst("Basic\\s+", "");
            String string = new String(Base64.decodeBase64(base64), Charset.forName("UTF-8"));
            String array[] = string.split(":");
            if (array.length == 2&&user.equals(array[0]) && password.equals(array[1])) {
                return true;
            }
        }
        HttpServletResponse res1 = (HttpServletResponse) res;
        res1.setStatus(HttpServletResponse.SC_UNAUTHORIZED);//401
        res1.setCharacterEncoding("UTF-8");
        res1.setHeader("WWW-Authenticate", "Basic realm=\"DAV\"");
        return false;
    }


    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        if (auth(req, res)) {
            super.service(req, res);
        }
    }
}

可以看到已经可以web访问了 

http://localhost:8080/WebDav

输入账号密码

接下来是windows挂载:

1. 修改注册表

打开cmd 输入regedit 

修改

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters

值为2 (默认windows下面不支持basic认证http,只支持https毕竟不安全)

2. 重启webclient

打开管理员CMD

net stop webclient

net start webclient

3. 挂载

net use * http://localhost:8080/WebDav 

输入账号密码 挂载成功。

 6. 备注

这个问题基本上是没修改注册表。也有可能windows webclient版本有问题。可以下个专业的webdav客户端。

7. 感想

1. 其实可以做到每个user一个目录。

2. 可以根据源码自己写个webdav连接hdfs minio等存储。等以后有时间可以看看(咕咕)。

### 回答1: AliyunDrive-WebDAV是阿里云盘提供的一种WebDAV协议接口,可以通过WebDAV客户端将阿里云盘挂载到本地文件系统中,实现云盘与本地文件系统的无缝连接。这样,用户就可以像访问本地文件一样访问云盘中的文件,方便快捷。 ### 回答2: AliyunDrive-WebDAV是阿里云推出的一项服务,旨在为用户提供能够在云端存储和管理数据的功能。通过AliyunDrive-WebDAV,用户可以将自己的文件存储在阿里云的云端服务器上,并通过WebDAV协议进行文件传输和管理。 WebDAV协议是一种基于HTTP协议的扩展协议,旨在使用户能够直接访问和管理Web服务器上的文件,而不必像FTP等协议一样需要通过专用的客户端。通过AliyunDrive-WebDAV,用户只需要使用标准的WebDAV客户端软件,就能够将自己的文件上传到阿里云服务器上,并对文件进行管理和分享。 与其他云存储服务相比,AliyunDrive-WebDAV具有许多优势。首先,阿里云作为中国领先的云计算企业,具有高度的安全性和稳定性,能够为用户提供一个高效稳定、免费的云存储服务。其次,通过WebDAV协议,用户可以像在本地计算机上一样方便地访问和管理自己的云文件,无需下载和安装额外的客户端软件。 在使用AliyunDrive-WebDAV时,用户可以根据自己的需求创建不同的存储空间,将文件按类别或用途分开存储,并可以对不同的存储空间设置不同的访问权限,确保文件的安全性。此外,用户还可以通过多种方式对自己的文件进行分享和协作,例如生成公共链接、设置密码和有效期限等。 总之,AliyunDrive-WebDAV一个非常优秀的云存储服务,具有高度的安全性和稳定性,能够帮助用户方便地管理自己的云文件,并在需要的时候与他人分享和协作。如果你需要云存储服务,不妨尝试一下AliyunDrive-WebDAV,相信它一定能够为你带来意想不到的方便和便利。 ### 回答3: AliyunDrive-WebDAV是一种基于WebDAV协议的阿里云网盘客户端。WebDAV(Web Distributed Authoring and Versioning)是一种基于HTTP协议的文件传输协议,允许用户通过Internet对文件进行读写操作,并支持文件的版本控制。 阿里云网盘是一款云存储服务,可用于存储、备份和共享文件,可以访问私有文件夹和共享文件夹。aliyundrive-webdav通过WebDAV协议与阿里云网盘进行交互,使用WebDAV协议,使用户可以从系统文件管理器中以本地文件夹的形式方便地访问和管理云端文件。 阿里云网盘与其他云存储服务相比的一个重要优点是其高级别的数据安全性能。每个阿里云账户都会自动为用户的所有文件提供多重保护,包括数据加密、自动备份和自动同步等功能。此外,阿里云网盘还提供“容器分离”技术,确保每个文件夹的安全性能。 使用aliyundrive-webdav,用户可以方便地将阿里云存储服务的所有功能带到他们的本地系统中。该工具在多个平台上都可用,包括Windows、MacOS和Linux。此外,aliyundrive-webdav还提供了本地文件夹的镜像同步功能,可以全自动地将云端文件夹与本地文件夹保持同步。 总之,AliyunDrive-WebDAV是一款创新性的云存储客户端,它在WebDAV协议的帮助下,方便了用户将阿里云网盘中的所有功能带到他们的本地系统中。它不仅方便了用户的使用,而且提高了安全性。该工具是现代云计算世界中的一个方便、高效、安全的工具。
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值