#http-api #ship #http #urbit #library #authentication #airlock

urbit-http-api

包装 Urbit 船 http api,以易于使用的 Rust 包形式暴露

24 个版本 (5 个重大变更)

0.7.3 2021 年 10 月 24 日
0.7.2 2021 年 6 月 21 日
0.7.1 2021 年 4 月 9 日
0.6.0 2021 年 3 月 13 日

#1530 in Web 编程

Download history 1/week @ 2024-04-24 1/week @ 2024-05-29 12/week @ 2024-06-26 141/week @ 2024-07-03 1/week @ 2024-07-24

每月 154 次下载
用于 urbit-chatbot-framework

MIT 许可证

120KB
2.5K SLoC

Rust Urbit HTTP API

此库包装了 Urbit 船的 http 接口,并以易于使用的 Rust 包形式暴露。

awesome urbit badge

所有实现细节,如认证 cookie、EventSource 连接、跟踪消息 ID 等,都自动为您处理,从而极大地提高了编写与 Urbit 船交互的 Rust 应用的体验。

此包目前允许开发者

  1. 授权自身并与船建立通道。
  2. 订阅任何应用程序/路径,以便可以读取船内正在发生的事件。
  3. 发布 pokes/scries/threads。
  4. 支持图存储,并具有本机 Rust Graph 接口,用于处理图。
  5. 为 Urbit 聊天提供简单的 Rust 接口。
  6. 为 Urbit 笔记本提供简单的 Rust 接口。

基本设计

该库公开了 3 个主要结构,用于与 Urbit 船交互

  1. ShipInterface
  2. Channel
  3. Subscription

通过 Channel 创建 Subscription,而 Channel 是通过 ShipInterface 创建的。换句话说,您需要先使用 ShipInterface 连接到 Urbit 船,然后才能启动消息 Channel,在您可以为应用程序/路径创建 Subscription 之前。

ShipInterface

ShipInterface 公开了一些有用的方法,这些方法在创建应用程序时将非常有用。

以下常用方法允许您创建新的 ShipInterface(从而授权自身与船),并创建新的 Channel

/// Logs into the given ship and creates a new `ShipInterface`.
/// `ship_url` should be `http://ip:port` of the given ship. Example:
/// `http://0.0.0.0:8080`. `ship_code` is the code acquire from your ship
/// by typing `+code` in dojo.
pub fn new(ship_url: &str, ship_code: &str) -> Result<ShipInterface>;

/// Create a `Channel` using this `ShipInterface`
pub fn create_channel(&mut self) -> Result<Channel>;

您还可以通过蜘蛛进行窥视和运行线程。

/// Send a scry using the `ShipInterface`
pub fn scry(&self, app: &str, path: &str) -> Result<Response>;

/// Run a thread via spider using the `ShipInterface`
pub fn spider(&self, input_mark: &str, output_mark: &str, thread_name: &str, body: &JsonValue) -> Result<Response>;

Channel

Channel 是最有用的结构,因为它包含与 pokes 和订阅相关的所有方法。

查看 Channel 结构体的定义,有助于理解其工作原理。

// A Channel which is used to interact with a ship
pub struct Channel<'a> {
    /// `ShipInterface` this channel is created from
    pub ship_interface: &'a ShipInterface,
    /// The uid of the channel
    pub uid: String,
    /// The url of the channel
    pub url: String,
    // The list of `Subscription`s for this channel
    pub subscription_list: Vec<Subscription>,
    // / The `EventSource` for this channel which reads all of
    // / the SSE events.
    event_receiver: ReceiverSource,
    /// The current number of messages that have been sent out (which are
    /// also defined as message ids) via this `Channel`
    pub message_id_count: u64,
}

一旦创建了一个 Channel,就会在单独的线程上创建一个与飞船的 EventSource 连接。这个线程接受所有传入的事件,并将它们排队在一个(Rust)无界通道上,该通道可以通过 event_receiver 内部访问。这个字段本身不是公开的,但在这个包中处理事件时,会使用一个针对应用程序开发者的更高级接口。

请注意,Channel 有一个 subscription_list。如你所见,每个 Channel 都公开了创建订阅的方法,这些方法会自动添加到 subscription_list 中。一旦创建了/添加了 Subscription 到列表中,Channel 显然将通过 SSE 接收事件消息(这些消息将在 event_receiver 中排队等待读取)。

从应用程序开发者的角度来看,你所要做的就是调用你的 Channel 上的 parse_event_messages 方法,所有排队的将事件都会被处理并传递到正确的 Subscriptionmessage_list。这对于在一个通道上创建了多个 Subscriptions 时非常有用,因为消息将自动为你预排序。

一旦解析了事件消息,就可以简单地调用 find_subscription 方法来与 Subscription 交互并读取其消息。

以下是一个 Channel 公开的实用方法。

/// Sends a poke over the channel
pub fn poke(&mut self, app: &str, mark: &str, json: &JsonValue) -> Result<Response>;

/// Create a new `Subscription` and thus subscribes to events on the ship with the provided app/path.
pub fn create_new_subscription(&mut self, app: &str, path: &str) -> Result<CreationID>;

/// Parses SSE messages for this channel and moves them into
/// the proper corresponding `Subscription`'s `message_list`.
pub fn parse_event_messages(&mut self);

/// Finds the first `Subscription` in the list which has a matching
/// `app` and `path`;
pub fn find_subscription(&self, app: &str, path: &str) -> Option<&Subscription>;

/// Finds the first `Subscription` in the list which has a matching
/// `app` and `path`, removes it from the list, and tells the ship
/// that you are unsubscribing.
pub fn unsubscribe(&mut self, app: &str, path: &str) -> Option<bool>;

/// Deletes the channel
pub fn delete_channel(self);

/// Exposes an interface for interacting with a ship's Graph Store directly.
pub fn graph_store(&mut self) -> GraphStore;

/// Exposes an interface for interacting with Urbit chats.
pub fn chat(&mut self) -> Chat;

/// Exposes an interface for interacting with Urbit notebooks.
pub fn notebook(&mute self) -> Notebook;

Subscription

如前所述,一个 Subscription 包含它自己的 message_list 字段,消息在 Channel 处理后存储在这里。

从应用程序开发者的角度来看,这是 Subscription 结构体的唯一有用特性。一旦获取,它就用于简单地读取消息。

为了提高消息阅读体验,Subscription 结构体公开了一个有用的方法。

/// Pops a message from the front of `Subscription`'s `message_list`.
/// If no messages are left, returns `None`.
pub fn pop_message(&mut self) -> Option<String>;

代码示例

戳击示例

此示例演示了如何使用 ShipInterface 连接到飞船,打开一个 Channel,在该通道上发出一个 poke,然后删除 Channel 以完成。

// Import the `ShipInterface` struct
use urbit_http_api::ShipInterface;

fn main() {
    // Create a new `ShipInterface` for a local ~zod ship
    let mut ship_interface =
        ShipInterface::new("http://0.0.0.0:8080", "lidlut-tabwed-pillex-ridrup").unwrap();
    // Create a `Channel`
    let mut channel = ship_interface.create_channel().unwrap();

    // Issue a poke over the channel
    let poke_res = channel.poke("hood", "helm-hi", &"This is a poke".into());

    // Cleanup/delete the `Channel` once finished
    channel.delete_channel();
}

图存储订阅示例

此示例演示了如何创建、交互和删除一个 Subscription。在这种情况下,我们希望通过 Subscription 读取来自图存储的所有新更新 10 秒钟,然后执行清理。

use std::thread;
use std::time::Duration;
use urbit_http_api::ShipInterface;

fn main() {
    // Create a new `ShipInterface` for a local ~zod ship
    let mut ship_interface =
        ShipInterface::new("http://0.0.0.0:8080", "lidlut-tabwed-pillex-ridrup").unwrap();
    // Create a `Channel`
    let mut channel = ship_interface.create_channel().unwrap();
    // Create a `Subscription` for the `graph-store` app with the `/updates` path. This `Subscription`
    // is automatically added to the `Channel`'s `subscription_list`.
    channel
        .create_new_subscription("graph-store", "/updates")
        .unwrap();

    // Create a loop that iterates 10 times
    for _ in 0..10 {
        // Parse all of the event messages to move them into the correct
        // `Subscription`s in the `Channel`'s `subscription_list`.
        channel.parse_event_messages();

        // Find our graph-store `Subscription`
        let gs_sub = channel.find_subscription("graph-store", "/updates").unwrap();

        // Pop all of the messages from our `gs_sub` and print them
        loop {
            let pop_res = gs_sub.pop_message();
            if let Some(mess) = &pop_res {
                println!("Message: {:?}", mess);
            }
            // If no messages left, stop
            if let None = &pop_res {
                break;
            }
        }

        // Wait for 1 second before trying to parse the event messages again
        thread::sleep(Duration::new(1, 0));
    }

    // Once finished, unsubscribe/destroy our `Subscription`
    channel.unsubscribe("graph-store", "/updates");
    // Delete the channel
    channel.delete_channel();
}

Urbit 聊天消息示例

此示例演示了如何使用 Chat 结构体接口连接到飞船并向 Urbit 聊天发送消息。

// Import the `ShipInterface` struct
use urbit_http_api::{ShipInterface, chat::Message};

fn main() {
    // Create a new `ShipInterface` for a local ~zod ship
    let mut ship_interface =
        ShipInterface::new("http://0.0.0.0:8080", "lidlut-tabwed-pillex-ridrup").unwrap();
    // Create a `Channel`
    let mut channel = ship_interface.create_channel().unwrap();

    // Create a `Message` which is formatted properly for an Urbit chat
    let message = Message::new()
        // Add text to your message
        .add_text("Checkout this cool article by ~wicdev-wisryt:")
        // Add a URL link to your message after the previous text (which gets automatically added on a new line)
        .add_url("https://urbit.org/blog/io-in-hoon/")
        // Add an image URL to your message after the previous url (which gets automatically added on a new line as a rendered image)
        .add_url("https://media.urbit.org/site/posts/essays/zion-canyon-1.jpg");
    // Send the message to a chat hosted by ~zod named "test-93".
    // Note the connected ship must already have joined the chat in order to send a message to the chat.
    let _mess_res = channel
        .chat()
        .send_message("~zod", "test-93", &message);

    // Cleanup/delete the `Channel` once finished
    channel.delete_channel();
}

Urbit 聊天订阅示例

此示例展示了如何利用更高级的 Chat 接口来订阅聊天并读取该聊天中发布的所有消息。

use std::thread;
use std::time::Duration;
use urbit_http_api::ShipInterface;

fn main() {
    // Create a new `ShipInterface` for a local ~zod ship
    let mut ship_interface =
        ShipInterface::new("http://0.0.0.0:8080", "lidlut-tabwed-pillex-ridrup").unwrap();
    // Create a `Channel`
    let mut channel = ship_interface.create_channel().unwrap();
    // Subscribe to a specific chat, and obtain a `Receiver` back which contains a stream of messages from the chat
    let chat_receiver = channel
        .chat()
        .subscribe_to_chat("~mocrux-nomdep", "test-93")
        .unwrap();

    // Create a loop that iterates 10 times
    for _ in 0..10 {
        // If a message has been posted to the chat, unwrap it and acquire the `AuthoredMessage`
        if let Ok(authored_message) = chat_receiver.try_recv() {
            // Pretty print the author ship @p and the message contents
            println!(
                "~{}:{}",
                authored_message.author,
                authored_message.message.to_formatted_string()
            );
        }
        // Wait for 1 second before checking again
        thread::sleep(Duration::new(1, 0));
    }

    // Delete the channel
    channel.delete_channel();
}

此库由 ~mocrux-nomdep([Robert Kornacki](https://github.com/robkorn))创建。

依赖关系

~10–23MB
~370K SLoC