You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Rust中为gio.OutputStream实现分块传输编码的包装器

如何在Rust中为gio.OutputStream实现分块传输编码的包装器

看起来你在给GIO的OutputStream做分块传输编码包装时,踩了GObject子类系统的坑——直接给普通Rust结构体实现OutputStreamExt是行不通的,因为GIO的类型都是基于GObject的,必须遵循glib的子类机制才能正确继承和扩展它们。我来帮你调整实现方式:

问题根源

GIO的OutputStream是GObject体系下的类型,OutputStreamExt这个trait要求实现者必须满足IsA<OutputStream>约束,而这个约束只有GObject的子类才能满足,普通的Rust结构体是没法直接实现的。所以我们需要用glib提供的子类框架来创建一个真正的OutputStream子类。

正确的实现方案

下面是完整的可编译实现,按照GObject子类的规范来编写:

use gio::prelude::*;
use gio::{glib, OutputStream, IOErrorEnum};
use glib::subclass::prelude::*;
use std::io;
use std::cell::RefCell;
use std::rc::Rc;

// 定义我们的分块输出流子类
#[derive(Default)]
pub struct ChunkedOutputStream {
    inner: RefCell<Option<Rc<OutputStream>>>,
}

// 定义子类的Class结构体,这里不需要额外字段,用默认即可
#[glib::object_subclass]
impl ObjectSubclass for ChunkedOutputStream {
    const NAME: &'static str = "ChunkedOutputStream";
    type Type = super::ChunkedOutputStream;
    type ParentType = OutputStream;
}

// 实现ObjectImpl,这里不需要重写任何方法,用默认实现即可
impl ObjectImpl for ChunkedOutputStream {}

// 实现OutputStreamImpl,重写我们需要的write和close方法
impl OutputStreamImpl for ChunkedOutputStream {
    fn write(
        &self,
        buffer: &[u8],
        _cancellable: Option<&glib::Cancellable>,
    ) -> Result<usize, glib::Error> {
        let inner = self.inner.borrow().as_ref().ok_or_else(|| {
            glib::Error::new(IOErrorEnum::Failed, "Inner output stream not set")
        })?;

        // 调用我们的分块写入逻辑
        self.write_chunk(inner, buffer)
            .map_err(|e| glib::Error::new(IOErrorEnum::Failed, &e.to_string()))
    }

    fn close(
        &self,
        _cancellable: Option<&glib::Cancellable>,
    ) -> Result<(), glib::Error> {
        let inner = self.inner.borrow().as_ref().ok_or_else(|| {
            glib::Error::new(IOErrorEnum::Failed, "Inner output stream not set")
        })?;

        // 写入最终的空分块并关闭
        self.write_final_chunk(inner)
            .map_err(|e| glib::Error::new(IOErrorEnum::Failed, &e.to_string()))?;
        
        inner.close(None)
            .map_err(|e| glib::Error::new(IOErrorEnum::Failed, &e.to_string()))
    }
}

// 为我们的子类添加公共方法和辅助方法
glib::wrapper! {
    pub struct ChunkedOutputStream(ObjectSubclass<imp::ChunkedOutputStream>) @extends OutputStream;
}

impl ChunkedOutputStream {
    pub fn new(inner: OutputStream) -> Self {
        let stream: Self = glib::Object::new();
        *stream.imp().inner.borrow_mut() = Some(Rc::new(inner));
        stream
    }

    fn write_chunk(&self, inner: &OutputStream, chunk: &[u8]) -> Result<usize, io::Error> {
        // 写入分块大小(十六进制)+ CRLF
        let chunk_size = format!("{:X}\r\n", chunk.len());
        inner.write_all(chunk_size.as_bytes(), None)
            .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;

        // 写入分块数据
        inner.write_all(chunk, None)
            .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;

        // 写入分块后的CRLF
        inner.write_all(b"\r\n", None)
            .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;

        Ok(chunk.len())
    }

    fn write_final_chunk(&self, inner: &OutputStream) -> Result<(), io::Error> {
        // 写入最终的0分块,标志传输结束
        inner.write_all(b"0\r\n\r\n", None)
            .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
        Ok(())
    }
}

关键说明

  • 我们用#[glib::object_subclass]宏来定义GObject子类,指定父类为OutputStream
  • 通过glib::wrapper!宏生成对外公开的类型,这个类型会自动实现IsA<OutputStream>,从而可以使用OutputStreamExt的所有方法
  • 内部用RefCellRc来管理传入的inner输出流,保证内部可变性和所有权安全
  • 重写OutputStreamImpl里的writeclose方法,实现分块传输的核心逻辑
  • 错误处理部分把GIO的glib::Error转换成标准库的io::Error,再转换回glib::Error统一对外抛出

关于现成实现的说明

目前GIO生态里没有专门的分块传输编码OutputStream实现,不过你可以先在crates.io搜索关键词chunked transfer encoding rust,如果是HTTP场景下的分块编码,hyper等HTTP库已经内置了相关逻辑,但如果是要适配GIO的OutputStream,上面的自定义子类实现是最直接的方案。

备注:内容来源于stack exchange,提问作者Daniel Andres Pelaez Lopez

火山引擎 最新活动