Skip to main content

ic_canister_kit/
http.rs

1use std::collections::HashMap;
2
3use candid::CandidType;
4use serde::{Deserialize, Serialize};
5
6pub use ic_management_canister_types::{
7    HttpHeader, HttpMethod, HttpRequestArgs, HttpRequestResult, TransformArgs, TransformContext,
8};
9
10use crate::{canister::types::CanisterCallError, identity::CanisterId};
11
12// ========================= HTTP 相关结构体 =========================
13
14/// 最长的响应体 3M, 留点空间给其他数据 此处大概 2.9375 MB
15pub const MAX_RESPONSE_LENGTH: usize = 1024 * 1024 * 3 - 1024 * 64;
16
17/// http 请求的结构体
18#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
19pub struct CustomHttpRequest {
20    /// 请求路径
21    pub url: String,
22
23    /// 请求类型
24    pub method: String,
25
26    /// 请求头
27    pub headers: HashMap<String, String>,
28
29    /// 请求体
30    pub body: Vec<u8>,
31}
32
33/// 流式响应的传递 token
34#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
35pub struct StreamingCallbackToken {
36    /// url 定位哪个请求
37    pub path: String,
38
39    /// 继续请求需要使用的标识
40    pub token: HashMap<String, String>,
41}
42
43/// 流式响应的响应体
44#[derive(CandidType, Serialize, Deserialize, Debug, Clone, Default)]
45pub struct StreamingCallbackHttpResponse {
46    ///  响应体
47    pub body: Vec<u8>,
48
49    /// 下一次流式回调需要使用的 token,`None` 表示响应结束
50    pub token: Option<StreamingCallbackToken>,
51}
52
53impl StreamingCallbackHttpResponse {
54    /// 空响应体
55    pub fn empty() -> Self {
56        Self::default()
57    }
58}
59
60/// 定义流回调函数
61#[allow(missing_docs)]
62mod callback {
63    use super::*;
64
65    candid::define_function!(pub HttpRequestStreamingCallback : (StreamingCallbackToken) -> (StreamingCallbackHttpResponse) query);
66}
67pub use callback::HttpRequestStreamingCallback;
68
69/// 流式响应的启动策略
70#[derive(CandidType, Deserialize, Debug, Clone)]
71pub enum StreamingStrategy {
72    /// 回调函数
73    Callback {
74        /// 回调方法
75        callback: HttpRequestStreamingCallback, // 回调方法
76
77        /// 回调参数token,用于识别哪个请求的回调
78        token: StreamingCallbackToken,
79    },
80}
81
82/// http 响应的结构体
83#[derive(CandidType, Debug, Clone)]
84pub struct CustomHttpResponse {
85    /// 响应状态码
86    pub status_code: u16,
87
88    /// 响应头
89    pub headers: HashMap<String, String>,
90
91    /// 响应体
92    pub body: Vec<u8>,
93
94    /// 如果有额外的数据需要通过流的方式继续传输 每个 http 请求最大只能支持 3M 的响应数据,因此太大的话需要采用此种方式
95    pub streaming_strategy: Option<StreamingStrategy>, // 如果需要使用流式响应
96
97    /// 是否将 HTTP query 请求升级为 update 调用
98    pub upgrade: Option<bool>,
99}
100
101// ====================== http 请求 ======================
102
103/// 可以调用罐子自身的 query 方法解析响应体
104pub fn http_transform(response: TransformArgs) -> HttpRequestResult {
105    let mut t = response.response;
106    t.headers = vec![];
107    t
108}
109
110// ====================== 对外发起 http 请求 ======================
111
112/// http 请求
113pub async fn do_http_request(
114    arg: HttpRequestArgs,
115    cycles: u128,
116) -> super::types::CanisterCallResult<HttpRequestResult> {
117    let cost = ic_cdk_management_canister::cost_http_request(&arg);
118    if cycles < cost {
119        return Err(CanisterCallError {
120            canister_id: CanisterId::management_canister(),
121            method: "ic#http_request".to_string(),
122            message: format!("Insufficient cycles. cost: {}, provided: {}", cost, cycles),
123        });
124    }
125    ic_cdk_management_canister::http_request(&arg)
126        .await
127        .map_err(|err| CanisterCallError {
128            canister_id: CanisterId::management_canister(),
129            method: "ic#http_request".to_string(),
130            message: err.to_string(),
131        })
132}
133
134/// 带有转换函数的 http 请求
135#[allow(clippy::future_not_send)]
136pub async fn do_http_request_with_closure(
137    arg: HttpRequestArgs,
138    cycles: u128,
139    transform_func: impl FnOnce(HttpRequestResult) -> HttpRequestResult + 'static,
140) -> super::types::CanisterCallResult<HttpRequestResult> {
141    let cost = ic_cdk_management_canister::cost_http_request(&arg);
142    if cycles < cost {
143        return Err(CanisterCallError {
144            canister_id: CanisterId::management_canister(),
145            method: "ic#http_request".to_string(),
146            message: format!("Insufficient cycles. cost: {}, provided: {}", cost, cycles),
147        });
148    }
149    ic_cdk_management_canister::http_request_with_closure(&arg, transform_func)
150        .await
151        .map_err(|err| CanisterCallError {
152            canister_id: CanisterId::management_canister(),
153            method: "ic#http_request".to_string(),
154            message: err.to_string(),
155        })
156}