Rust SFTP客户端库russh-sftp的使用,实现安全高效的文件传输与远程操作

Russh SFTP

SFTP子系统支持的服务器和客户端,用于Russh及更多!

该crate可以提供与任何能够提供子系统通道原始数据输入输出的兼容性。 根据版本3规范(最流行的)实现。

项目的主要思想是提供在任何级别与协议交互的实现。

示例

  • 客户端示例
  • 简单服务器

已实现的功能?

  • 基本数据包
  • 扩展数据包
  • 文件属性简化
  • 客户端
  • 客户端示例
  • 服务器端
  • 简单服务器示例
  • 扩展支持:limits@openssh.comhardlink@openssh.comfsync@openssh.comstatvfs@openssh.com
  • 完整服务器示例
  • 单元测试
  • 工作流

采用者

  • kty - Kubernetes的终端。

一些话

感谢@Eugeny(Russh的作者)的及时帮助和Russh API的最终确定

以下是基于内容提供的示例,整理出的完整Rust SFTP客户端使用示例:

use russh::{client, ChannelMsg};
use russh_keys::load_secret_key;
use russh_sftp::protocol::FileAttributes;
use std::io::Write;
use std::path::Path;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 配置SSH客户端
    let config = client::Config {
        connection_timeout: Some(Duration::from_secs(10)),
        ..Default::default()
    };
    
    // 加载SSH密钥
    let key = load_secret_key("path/to/private/key", None)?;
    
    // 连接到SFTP服务器
    let mut session = client::connect(
        config,
        ("sftp.example.com", 22),
        "username",
        key,
    ).await?;
    
    // 打开SFTP子系统
    let mut channel = session.channel_open_session().await?;
    channel.request_subsystem(true, "sftp").await?;
    
    // 创建SFTP客户端
    let sftp = russh_sftp::client::Client::new(channel.into_stream());
    
    // 示例1: 列出远程目录
    let files = sftp.readdir(Path::new("/remote/path")).await?;
    for file in files {
        println!("File: {:?}", file);
    }
    
    // 示例2: 上传文件
    let local_path = Path::new("local_file.txt");
    let remote_path = Path::new("/remote/path/remote_file.txt");
    let mut local_file = std::fs::File::open(local_path)?;
    let mut remote_file = sftp.create(remote_path).await?;
    std::io::copy(&mut local_file, &mut remote_file)?;
    
    // 示例3: 下载文件
    let mut remote_file = sftp.open(remote_path).await?;
    let mut local_file = std::fs::File::create("downloaded_file.txt")?;
    std::io::copy(&mut remote_file, &mut local_file)?;
    
    // 示例4: 创建目录
    sftp.mkdir(Path::new("/remote/path/new_dir"), FileAttributes::default()).await?;
    
    // 示例5: 删除文件
    sftp.remove(Path::new("/remote/path/file_to_delete.txt")).await?;
    
    // 关闭连接
    session.disconnect(None, "").await?;
    
    Ok(())
}

此示例展示了如何使用russh-sftp库进行基本的SFTP操作,包括连接服务器、文件传输和目录操作。代码中包含了必要的错误处理和异步操作,确保安全高效的文件传输。

以下是一个更完整的SFTP客户端示例,包含更多实用功能:

use russh::{client, ChannelMsg};
use russh_keys::load_secret_key;
use russh_sftp::protocol::{FileAttributes, OpenOptions, StatusCode};
use std::io::{Read, Write};
use std::path::Path;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // SSH客户端配置
    let config = client::Config {
        connection_timeout: Some(Duration::from_secs(30)),
        auth_timeout: Some(Duration::from_secs(15)),
        ..Default::default()
    };

    // 加载私钥文件
    let key_path = "~/.ssh/id_rsa"; // 替换为实际私钥路径
    let key = load_secret_key(key_path, None)?;

    // 建立SSH连接
    let mut session = client::connect(
        config,
        ("sftp.server.com", 22), // 替换为实际SFTP服务器地址
        "your_username",         // 替换为实际用户名
        key,
    ).await?;

    // 打开会话通道并请求SFTP子系统
    let mut channel = session.channel_open_session().await?;
    channel.request_subsystem(true, "sftp").await?;

    // 创建SFTP客户端实例
    let sftp = russh_sftp::client::Client::new(channel.into_stream());

    // 1. 获取当前工作目录
    let current_dir = sftp.realpath(Path::new(".")).await?;
    println!("当前工作目录: {:?}", current_dir);

    // 2. 列出目录内容
    println!("\n目录列表:");
    let entries = sftp.readdir(Path::new("/")).await?;
    for entry in entries {
        println!("  {:?}", entry);
    }

    // 3. 创建新目录
    let new_dir = Path::new("/test_directory");
    match sftp.mkdir(new_dir, FileAttributes::default()).await {
        Ok(_) => println!("\n目录创建成功: {:?}", new_dir),
        Err(e) => println!("\n目录创建失败: {}", e),
    }

    // 4. 文件上传示例
    let local_file = "local_test.txt";
    let remote_file = Path::new("/test_directory/uploaded_file.txt");
    
    // 创建本地测试文件
    std::fs::write(local_file, "Hello SFTP from Rust!")?;

    // 上传文件
    let mut local_data = std::fs::File::open(local_file)?;
    let mut remote_handle = sftp.create(remote_file).await?;
    std::io::copy(&mut local_data, &mut remote_handle)?;
    println!("\n文件上传成功: {:?}", remote_file);

    // 5. 文件下载示例
    let download_path = "downloaded_file.txt";
    let mut remote_handle = sftp.open(remote_file).await?;
    let mut local_handle = std::fs::File::create(download_path)?;
    std::io::copy(&mut remote_handle, &mut local_handle)?;
    println!("文件下载成功: {}", download_path);

    // 6. 获取文件属性
    let attrs = sftp.stat(remote_file).await?;
    println!("\n文件属性: {:?}", attrs);

    // 7. 重命名文件
    let new_remote_path = Path::new("/test_directory/renamed_file.txt");
    sftp.rename(remote_file, new_remote_path).await?;
    println!("文件重命名成功");

    // 8. 删除文件
    sftp.remove(new_remote_path).await?;
    println!("文件删除成功");

    // 9. 删除目录
    sftp.rmdir(new_dir).await?;
    println!("目录删除成功");

    // 关闭SFTP会话
    session.disconnect(None, "SFTP会话结束").await?;

    // 清理本地文件
    std::fs::remove_file(local_file)?;
    std::fs::remove_file(download_path)?;

    println!("\nSFTP操作完成!");
    Ok(())
}

此完整示例演示了SFTP客户端的各种常用操作,包括文件上传下载、目录管理、文件操作等,并包含了完整的错误处理和资源清理。


1 回复

Rust SFTP客户端库 russh-sftp 使用指南

概述

russh-sftp 是一个基于 Rust 语言开发的 SFTP(SSH File Transfer Protocol)客户端库,提供安全高效的文件传输和远程操作功能。该库构建在 russh SSH 库之上,支持完整的 SFTP 协议操作。

主要特性

  • 安全的 SSH 连接和身份验证
  • 完整的 SFTP 操作支持
  • 异步/同步文件传输
  • 目录操作和管理
  • 文件权限和属性管理
  • 高性能的并发传输

安装方法

在 Cargo.toml 中添加依赖:

[dependencies]
russh-sftp = "0.4"
tokio = { version = "1.0", features = ["full"] }

基本使用方法

1. 建立 SFTP 连接

use russh_sftp::client::{SftpClient, SftpOptions};
use russh_sftp::protocol::FileOpenOptions;
use tokio::net::TcpStream;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 建立 TCP 连接
    let stream = TcpStream::connect("example.com:22").await?;
    
    // 创建 SFTP 客户端
    let mut client = SftpClient::new(
        stream,
        SftpOptions::default()
            .username("your_username")
            .password("your_password")
    ).await?;
    
    // 执行操作...
    
    Ok(())
}

2. 文件上传示例

async fn upload_file(client: &mut SftpClient, local_path: &str, remote_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    // 打开本地文件
    let mut file = tokio::fs::File::open(local_path).await?;
    
    // 创建远程文件
    let remote_file = client
        .open(
            remote_path,
            FileOpenOptions::new()
                .write(true)
                .create(true)
                .truncate(true),
        )
        .await?;
    
    // 读取本地文件内容并写入远程文件
    let mut contents = Vec::new();
    file.read_to_end(&mut contents).await?;
    remote_file.write_all(&contents).await?;
    
    println!("文件上传成功: {}", remote_path);
    Ok(())
}

3. 文件下载示例

async fn download_file(client: &mut SftpClient, remote_path: &str, local_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    // 打开远程文件
    let mut remote_file = client
        .open(remote_path, FileOpenOptions::new().read(true))
        .await?;
    
    // 创建本地文件
    let mut local_file = tokio::fs::File::create(local_path).await?;
    
    // 读取远程文件内容并写入本地文件
    let mut contents = Vec::new();
    remote_file.read_to_end(&mut contents).await?;
    local_file.write_all(&contents).await?;
    
    println!("文件下载成功: {}", local_path);
    Ok(())
}

4. 目录操作示例

async fn list_directory(client: &mut SftpClient, path: &str) -> Result<(), Box<dyn std::error::Error>> {
    // 列出目录内容
    let entries = client.readdir(path).await?;
    
    for entry in entries {
        println!("名称: {}, 类型: {:?}, 大小: {} bytes", 
                entry.filename, 
                entry.attrs.file_type(),
                entry.attrs.size.unwrap_or(0));
    }
    
    Ok(())
}

async fn create_directory(client: &mut SftpClient, path: &str) -> Result<(), Box<dyn std::error::Error>> {
    client.mkdir(path, 0o755).await?;
    println!("目录创建成功: {}", path);
    Ok(())
}

5. 文件操作示例

async fn file_operations(client: &mut SftpClient) -> Result<(), Box<dyn std::error::Error>> {
    // 重命名文件
    client.rename("old_name.txt", "new_name.txt").await?;
    
    // 删除文件
    client.remove("file_to_delete.txt").await?;
    
    // 获取文件属性
    let attrs = client.stat("some_file.txt").await?;
    println!("文件大小: {} bytes", attrs.size.unwrap_or(0));
    
    Ok(())
}

错误处理

async fn handle_errors(client: &mut SftpClient) {
    match client.stat("nonexistent_file.txt").await {
        Ok(attrs) => println!("文件存在,大小: {}", attrs.size.unwrap_or(0)),
        Err(russh_sftp::error::Error::Sftp(russh_sftp::protocol::Status::NoSuchFile)) => {
            println!("文件不存在")
        }
        Err(e) => eprintln!("发生错误: {}", e),
    }
}

完整示例代码

use russh_sftp::client::{SftpClient, SftpOptions};
use russh_sftp::protocol::FileOpenOptions;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 建立 SFTP 连接
    let stream = TcpStream::connect("example.com:22").await?;
    
    let mut client = SftpClient::new(
        stream,
        SftpOptions::default()
            .username("your_username")
            .password("your_password")
    ).await?;

    // 创建测试目录
    create_directory(&mut client, "test_dir").await?;
    
    // 列出目录内容
    list_directory(&mut client, ".").await?;
    
    // 文件上传
    upload_file(&mut client, "local_file.txt", "test_dir/remote_file.txt").await?;
    
    // 文件下载
    download_file(&mut client, "test_dir/remote_file.txt", "downloaded_file.txt").await?;
    
    // 文件操作
    file_operations(&mut client).await?;
    
    // 错误处理示例
    handle_errors(&mut client).await;

    Ok(())
}

async fn upload_file(client: &mut SftpClient, local_path: &str, remote_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let mut file = tokio::fs::File::open(local_path).await?;
    let remote_file = client
        .open(
            remote_path,
            FileOpenOptions::new()
                .write(true)
                .create(true)
                .truncate(true),
        )
        .await?;
    
    let mut contents = Vec::new();
    file.read_to_end(&mut contents).await?;
    remote_file.write_all(&contents).await?;
    
    println!("文件上传成功: {}", remote_path);
    Ok(())
}

async fn download_file(client: &mut SftpClient, remote_path: &str, local_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let mut remote_file = client
        .open(remote_path, FileOpenOptions::new().read(true))
        .await?;
    
    let mut local_file = tokio::fs::File::create(local_path).await?;
    
    let mut contents = Vec::new();
    remote_file.read_to_end(&mut contents).await?;
    local_file.write_all(&contents).await?;
    
    println!("文件下载成功: {}", local_path);
    Ok(())
}

async fn list_directory(client: &mut SftpClient, path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let entries = client.readdir(path).await?;
    
    println!("目录 {} 内容:", path);
    for entry in entries {
        println!("名称: {}, 类型: {:?}, 大小: {} bytes", 
                entry.filename, 
                entry.attrs.file_type(),
                entry.attrs.size.unwrap_or(0));
    }
    
    Ok(())
}

async fn create_directory(client: &mut SftpClient, path: &str) -> Result<(), Box<dyn std::error::Error>> {
    client.mkdir(path, 0o755).await?;
    println!("目录创建成功: {}", path);
    Ok(())
}

async fn file_operations(client: &mut SftpClient) -> Result<(), Box<dyn std::error::Error>> {
    // 创建测试文件
    let test_file = client
        .open(
            "test_file.txt",
            FileOpenOptions::new()
                .write(true)
                .create(true),
        )
        .await?;
    test_file.write_all(b"Hello, SFTP!").await?;
    
    // 重命名文件
    client.rename("test_file.txt", "renamed_file.txt").await?;
    
    // 获取文件属性
    let attrs = client.stat("renamed_file.txt").await?;
    println!("重命名文件大小: {} bytes", attrs.size.unwrap_or(0));
    
    // 删除文件
    client.remove("renamed_file.txt").await?;
    
    Ok(())
}

async fn handle_errors(client: &mut SftpClient) {
    match client.stat("nonexistent_file.txt").await {
        Ok(attrs) => println!("文件存在,大小: {}", attrs.size.unwrap_or(0)),
        Err(russh_sftp::error::Error::Sftp(russh_sftp::protocol::Status::NoSuchFile)) => {
            println!("文件不存在")
        }
        Err(e) => eprintln!("发生错误: {}", e),
    }
}

性能优化建议

  1. 使用缓冲区:对于大文件传输,使用适当的缓冲区大小
  2. 并发传输:利用异步特性进行多个文件的并发传输
  3. 连接复用:保持连接打开以进行多个操作
  4. 错误重试:实现适当的重试机制处理网络波动

注意事项

  • 确保服务器支持 SFTP 协议
  • 正确处理连接超时和重连
  • 注意文件权限和所有权问题
  • 在生产环境中使用适当的日志记录和监控

这个库提供了强大而灵活的 SFTP 操作功能,结合 Rust 的安全性和性能优势,是构建可靠文件传输应用的优秀选择。

回到顶部