一个用TypeScript编写的WebDAV客户端,适用于NodeJS和浏览器
WebDAV是一种广为人知、稳定且高度灵活的协议,用于通过API与远程文件系统进行交互。由于其广泛使用,许多文件托管服务如Nextcloud/ownCloud、Box和Yandex都将其作为主要接口的备选方案。
这个库提供了一个WebDAV客户端接口,使与支持WebDAV的服务交互变得简单。API返回promise并解析结果。它解析并准备目录内容请求以便于使用,同时还提供了获取文件统计信息和配额等方法。
这个库的目标不是严格遵循RFC或标准的WebDAV接口,而是提供一个易于使用的客户端API,用于在Node或浏览器中与大多数WebDAV服务进行交互。
版本5正在积极开发中。版本4处于维护模式,将接收安全和稳定性相关的错误修复。早期版本已经废弃,不会再收到更新。
版本5将库升级为使用ESM(ECMAScript模块),因此你的环境必须符合以下格式之一才能使用这个库:
package.json
中设置"type": "module"
(ESM模式)如果你还没准备好升级,可以考虑使用这个库的版本4。
这个库使用@buttercup/fetch
以跨平台的方式发起请求。如果存在,它会使用浏览器的原生fetch
,否则使用polyfill。在Node和其他环境中,它使用node-fetch
。
v5之前的版本使用Axios发起请求。
支持表:
库主版本 | Node JS范围 |
---|---|
v5 | 14+ |
v4 | 10-18 |
v3 | 10-16 |
v2 | 6-14 |
v1 | 4-12 |
从这个库的版本3开始支持浏览器环境。
如前所述,v5引入了ESM,这可能需要在为浏览器打包时进行额外配置。
尽管你可以选择自行转译这个库的默认入口点(NodeJS),但不建议这样做 - 请使用专门的web版本。
在版本4中,你必须为web版本使用不同的入口点,虽然在版本5中仍可以这样做,但你不再需要:
import { createClient } from "webdav/web"; // 或 import { createClient } from "webdav"; // 在支持的打包工具中两种方式都可以
版本3/4支持浏览器中的UMD风格模块,但在版本5中不再支持。版本5只提供一个可以导入到其他支持ESM的项目中 的ESM兼容包。
**注意:**在浏览器中不支持流,所以createReadStream
和createWriteStream
只是存根。调用它们会抛出异常。
自版本5.6.0
起,React-Native得到了更好的支持,使用了专门为该平台构建的版本。导入应该是自动的,但可以通过直接从/react-native
导入来强制使用:
import { createClient } from "webdav/react-native";
**注意:**由于某些原因,Metro构建系统无法正确解析这些入口,你可能需要为React Native自定义babel配置以正确导入这个库:
module.exports = { presets: ["module:metro-react-native-babel-preset"], plugins: [ [ "module-resolver", { alias: { // 将webdav客户端入口指向react native构建: webdav: "webdav/dist/react-native" }, extensions: [".tsx", ".ts", ".js", ".jsx", ".json"] } ] ] };
使用这个覆盖后,你可以简单地从webdav
导入。
这个库为Node构建导出了Typescript类型。所有类型也可以直接从模块中导入:
import { AuthType, createClient } from "webdav"; const client = createClient("https://some-server.org", { authType: AuthType.Digest, username: "user", password: "pass" });
使用npm简单地安装为依赖:
npm install webdav --save
使用方法是通过调用工厂函数createClient
创建一个客户端适配器实例:
const { createClient } = require("webdav"); const client = createClient( "https://webdav.example.com/marie123", { username: "marie", password: "myS3curePa$$w0rd" } ); // 获取目录内容 const directoryItems = await client.getDirectoryContents("/"); // 输出一个类似这样的结构: // [{ // filename: "/my-file.txt", // basename: "my-file.txt", // lastmod: "Mon, 10 Oct 2018 23:24:11 GMT", // size: 371, // type: "file" // }]
如果没有提供authType
配置参数,WebDAV客户端会自动检测使用哪种认证方式,在AuthType.None
和AuthType.Password
之间选择。对于AuthType.Token
或AuthType.Digest
,你必须明确指定。
设置authType
将自动管理连接时的Authorization
头。
如果你不确定远程服务器是需要digest还是password认证,可以将authType
设置为AuthType.Auto
。
如果服务器不需要认证,你可以不使用认证 - 只需在配置中避免传递username
和password
的值。
要使用基本认证,只需在配置中传递username
和password
。
这个库还允许通过设置httpAgent
和httpsAgent
属性来覆盖内置的HTTP和HTTPS代理。这些应该是node的http.Agent和https.Agent的实例。
要使用令牌进行认证,将令牌数据传递给token
字段并指定authType
:
createClient( "https://address.com", { authType: AuthType.Token, token: { access_token: "2YotnFZFEjr1zCsicMWpAA", token_type: "example", expires_in: 3600, refresh_token: "tGzv3JOkF0XG5Qx2TlKWIA", example_parameter: "example_value" } } );
你也可以自己提供HA1(详情见此)。这使你能够在用户登录时生成HA1并保存它,这样你就不需要保存密码本身。
createClient("https://address.com", { authType: AuthType.Digest, username: "someUser", password: "", ha1: "在此处填入您之前生成的ha1" });
如果服务器需要基于摘要的认证,您可以通过 authType
配置参数启用此功能,并提供 username
和 password
:
createClient( "https://address.com", { authType: AuthType.Digest, username: "someUser", password: "myS3curePa$$w0rd" } );
createClient
方法接受一个 WebDAV 服务 URL 和一个配置选项参数。
可用的配置选项如下:
选项 | 默认值 | 描述 |
---|---|---|
authType | null | 要使用的认证类型。如果未提供,默认会根据是否提供了 username 和 password 来尝试检测。 |
contactHref | 此 URL | 用于 LOCK 的联系 URL。 |
headers | {} | 提供给所有请求的附加头部。这里提供的头部会被方法特定的头部覆盖,包括 Authorization 。 |
httpAgent | 无 | HTTP 代理实例。仅在 Node 中可用。参见 http.Agent。 |
httpsAgent | 无 | HTTPS 代理实例。仅在 Node 中可用。参见 https.Agent。 |
password | 无 | 用于认证的密码。 |
token | 无 | 用于认证的令牌对象。 |
username | 无 | 用于认证的用户名。 |
withCredentials | 无 | 请求的凭证包含设置。 |
WebDAVClient
接口类型包含了 WebDAV 客户端实例的所有方法和签名。
将文件从一个位置复制到另一个位置。
await client.copyFile( "/images/test.jpg", "/public/img/test.jpg" );
(filename: string, destination: string, options?: WebDAVMethodOptions) => Promise<void>
参数 | 是否必需 | 描述 |
---|---|---|
filename | 是 | 源文件名。 |
destination | 是 | 目标文件名。 |
options | 否 | 方法选项。 |
创建新目录。
await client.createDirectory("/data/system/storage");
(path: string, options?: CreateDirectoryOptions) => Promise<void>
参数 | 是否必需 | 描述 |
---|---|---|
path | 是 | 要创建的路径。 |
options | 否 | 创建目录选项。 |
options.recursive | 否 | 如果目录不存在,则递归创建。 |
options
扩展自 方法选项。
递归目录创建在请求方面是昂贵的。会发出多个 stat
请求(总数等于已存在路径的深度加1)以检测路径的哪些部分已经存在,直到找到一个不存在的段 - 然后只请求 创建 方法。
例如,递归调用创建路径 /a/b/c/d/e
,其中 /a/b
已经存在,将会导致 3 个 stat
请求(针对 /a
、/a/b
和 /a/b/c
)和 3 个 createDirectory
请求(针对 /a/b/c
、/a/b/c/d
和 /a/b/c/d/e
)。
同步创建远程文件的可读流。
注意,虽然流会立即返回,但连接和获取文件仍在后台异步执行。在流开始接收数据之前会有一些延迟。
client .createReadStream("/video.mp4") .pipe(fs.createWriteStream("~/video.mp4"));
如果您只想流式传输文件的一部分,可以在选项参数中指定 range
:
client .createReadStream( "/video.mp4", { range: { start: 0, end: 1024 } } ).pipe(fs.createWriteStream("~/video.mp4"));
(filename: string, options?: CreateReadStreamOptions) => Stream.Readable
参数 | 是否必需 | 描述 |
---|---|---|
callback | 否 | 用于触发请求响应的回调。 |
filename | 是 | 要流式传输的远程文件。 |
options | 否 | 读取流选项。 |
options.range | 否 | 流范围配置。 |
options.range.start | 是 | 流的起始字节位置。 |
options.range.end | 否 | 流的结束字节位置。 |
options
扩展自 方法选项。
创建针对远程文件的写入流。
注意,虽然流会立即返回,但连接和写入远程文件仍在后台异步执行。在流开始传输数据之前会有一些延迟。
fs .createReadStream("~/Music/song.mp3") .pipe(client.createWriteStream("/music/song.mp3"));
(filename: string, options?: CreateWriteStreamOptions, callback?: CreateWriteStreamCallback) => Stream.Writable
参数 | 是否必需 | 描述 |
---|---|---|
filename | 是 | 要流式写入的远程文件。 |
options | 否 | 写入流 选项。 |
options.overwrite | 否 | 如果远程文件已存在,是否覆盖。默认为 true 。 |
callback | 否 | 连接建立并开始流式传输后触发的回调。回调会收到请求的响应。 |
options
扩展自 方法选项。
可以通过调用 customRequest
向附加的主机发出自定义请求。自定义请求提供了客户端内部使用的样板认证和其他请求选项。
const resp: Response = await this.client.customRequest("/alrighty.jpg", { method: "PROPFIND", headers: { Accept: "text/plain", Depth: "0" } }); const result: DAVResult = await parseXML(await resp.text()); const stat: FileStat = parseStat(result, "/alrighty.jpg", false);
(path: string, requestOptions: RequestOptionsCustom) => Promise<Response>
参数 | 是否必需 | 描述 |
---|---|---|
path | 是 | 要发出自定义请求的路径。 |
requestOptions | 是 | 请求选项 - 必需的参数如 url 、method 等 - 请参考 RequestOptionsCustom 类型定义。 |
请求选项参数 不 扩展 方法选项,因为诸如 headers
之类的内容已经可以指定。
删除远程文件。
await client.deleteFile("/tmp.dat");
(filename: string, options?: WebDAVMethodOptions) => Promise<void>
参数 | 是否必需 | 描述 |
---|---|---|
filename | 是 | 要删除的文件。 |
options | 否 | 方法选项。 |
检查文件或目录是否存在。
if (await client.exists("/some/path") === false) { await client.createDirectory("/some/path"); }
(path: string, options?: WebDAVMethodOptions) => Promise<boolean>
参数 | 是否必需 | 描述 |
---|---|---|
path | 是 | 要检查的远程路径。 |
options | 否 | 方法选项。 |
获取远程目录的内容。返回 项目统计 数组。
// 获取当前目录内容: const contents = await client.getDirectoryContents("/"); // 获取所有内容: const contents = await client.getDirectoryContents("/", { deep: true });
文件可以使用 glob
选项进行通配(使用 minimatch
处理)。使用通配模式时,建议获取 deep
内容:
const images = await client.getDirectoryContents("/", { deep: true, glob: "/**/*.{png,jpg,gif}" });
(path: string, options?: GetDirectoryContentsOptions) => Promise<Array<FileStat> | ResponseDataDetailed<Array<FileStat>>>
参数 | 是否必需 | 描述 |
---|---|---|
path | 是 | 要获取内容的路径。 |
options | 否 | 配置选项。 |
options.deep | 否 | 获取深层结果(递归)。默认为 false 。 |
options.details | 否 | 获取详细结果(项目统计信息,头部)。默认为 false 。 |
options.glob | 否 | 用于匹配文件名的通配符字符串。默认不设置。 |
options
扩展自 方法选项。
获取远程文件的内容。默认返回二进制内容(Buffer
):
const buff: Buffer = await client.getFileContents("/package.zip");
如果传输的文件较大,建议使用流。
也可以获取文本文件:
const str: string = await client.getFileContents("/config.json", { format: "text" });
(filename: string, options?: GetFileContentsOptions) => Promise<BufferLike | string | ResponseDataDetailed<BufferLike | string>>
参数 | 是否必需 | 描述 |
---|---|---|
filename | 是 | 要获取内容的文件。 |
options | 否 | 配置选项。 |
options.details | 否 | 获取详细结果(附加头部)。默认为 false 。 |
options.format | 否 | 是获取二进制("binary")数据还是文本("text")。默认为 "binary"。 |
options
扩展自 方法选项。
生成可下载文件的公共链接。此方法是同步的。在 URL 中暴露身份验证详细信息。
并非所有服务器都支持此功能。只有基本身份验证和未经身份验证的连接支持此方法。
const downloadLink: string = client.getFileDownloadLink("/image.png");
(filename: string) => string
参数 | 是否必需 | 描述 |
---|---|---|
filename | 是 | 要生成下载链接的远程文件。 |
生成文件上传的 URL。此方法是同步的。在 URL 中暴露身份验证详细信息。
const uploadLink: string = client.getFileUploadLink("/image.png");
(filename: string) => string
参数 | 是否必需 | 描述 |
---|---|---|
filename | 是 | 要生成上传链接的远程文件。 |
获取当前账户的配额信息:
const quota: DiskQuota = await client.getQuota(); // { // "used": 1938743, // "available": "unlimited" // }
(options?: GetQuotaOptions) => Promise<DiskQuota | null | ResponseDataDetailed<DiskQuota | null>>
参数 | 是否必需 | 描述 |
---|---|---|
options | 否 | 配置选项。 |
options.details | 否 | 返回详细结果(头部等)。默认为 false 。 |
options.path | 否 | 用 于发出配额请求的路径。 |
options
扩展自 方法选项。
锁定远程资源(使用写入锁)。
const lock = await client.lock("/file.doc"); // 稍后 await client.unlock("/file.doc", lock.token);
(path: string, options?: LockOptions) => Promise<LockResponse>
参数 | 是否必需 | 描述 |
---|---|---|
path | 是 | 要锁定的路径。 |
options | 否 | 配置选项。 |
options.timeout | 否 | WebDAV 锁请求超时。参见 WebDAV Timeout 头部文档。 |
options.refreshToken | 否 | 应刷新的先前有效锁定令牌。 |
options
扩展自 方法选项。
将文件移动到另一个位置。
await client.moveFile("/file1.png", "/file2.png");
(filename: string, destinationFilename: string, options?: WebDAVMethodOptions) => Promise<void>
参数 | 是否必需 | 描述 |
---|---|---|
filename | 是 | 要移动的文件。 |
destinationFilename | 是 | 目标文件名。 |
options | 否 | 方法选项。 |
将数据写入远程文件。当文件未写入时返回 false
(例如 { overwrite: false }
且文件已存在),否则返回 true
。
// 写入缓冲区: await client.putFileContents("/my/file.jpg", imageBuffer, { overwrite: false }); // 写入文本文件: await client.putFileContents("/my/file.txt", str);
(filename: string, data: string | BufferLike | Stream.Readable, options?: PutFileContentsOptions) => Promise<boolean>
参数 | 是否必需 | 描述 |
---|---|---|
filename | 是 | 要写入的文件。 |
data | 是 | 要写入的数据。可以是字符串、缓冲区或可读流。 |
options | 否 | 配置选项。 |
options.contentLength | 否 | 数据内容长度覆盖。可以是布尔值(true (默认)= 计算,false = 不设置)或表示文件确切字节长度的数字。 |
options.overwrite | 否 | 如果远程文件存在,是否覆盖。默认为 true 。 |
options
扩展自 方法选项。
使用部分更新来更新远程文件。此方法适用于更新文件而无需下载和重新上传整个文件。
请注意,此方法未标准化,可能并非所有服务器都支持。 要使用此功能,必须满足以下条件之一:
(filePath: string, start: number, end: number, data: string | BufferLike | Stream.Readable, options?: WebDAVMethodOptions)=> Promise<void>
参数 | 是否必需 | 描述 |
---|---|---|
filePath | 是 | 要更新的文件。 |
start | 是 | 起始字节位置。(包含) |
end | 是 | 结束字节位置。(包含) |
data | 是 | 要写入的数据。可以是字符串、缓冲区或可读流。 |
options | 否 | 配置选项。 |
根据 rfc5323 执行 WebDAV 搜索。
const searchRequest = ` <?xml version="1.0" encoding="UTF-8"?> <d:searchrequest xmlns:d="DAV:" xmlns:f="http://example.com/foo"> <f:natural-language-query> 查找上周更改的文件 </f:natural-language-query> </d:searchrequest> ` const result: SearchResult = await client.search("/some-collection", { data: searchRequest });
(path: string, options?: SearchOptions) => Promise<SearchResult | ResponseDataDetailed<SearchResult>>
参数 | 是否必需 | 描述 |
---|---|---|
path | 是 | 执行搜索的远程路径。 |
options | 否 | 配置选项。 |
options.details | 否 | 返回详细结果(头部等)。默认为 false 。 |
options 继承自方法选项。 |
获取文件或目录的 stat 对象。返回一个项目统计信息。
const stat: FileStat = await client.stat("/some/file.tar.gz");
(path: string, options?: StatOptions) => Promise<FileStat | ResponseDataDetailed<FileStat>>
参数 | 是否必需 | 描述 |
---|---|---|
path | 是 | 要获取统计信息的远程路径。 |
options | 否 | 配置选项。 |
options.details | 否 | 返回详细结果(包括头部等)。默认为 false 。 |
options
继承自方法选项。
使用令牌解锁被锁定的资源。
await client.unlock("/file.doc", lock.token);
(path: string, token:string, options?: WebDAVMethodOptions) => Promise<void>
参数 |
---|