Skip to main content

steamkit/steam/webapi/
content_server_directory_service.rs

1//! Loading content servers from the `IContentServerDirectoryService` Web API.
2
3use steamkit_protos::steam::{
4    CContentServerDirectoryGetServersForSteamPipeRequest,
5    CContentServerDirectoryGetServersForSteamPipeResponse,
6};
7use tokio_util::sync::CancellationToken;
8
9use crate::error::SteamError;
10use crate::steam::cdn::server::{ConnectionProtocol, Server};
11use crate::steam::steam_client::configuration::steam_configuration::SteamConfiguration;
12
13use super::steam_configuration_web_api_extensions::get_async_web_api_interface;
14
15/// Loads content servers from the `IContentServerDirectoryService` Web API.
16pub struct ContentServerDirectoryService;
17
18impl ContentServerDirectoryService {
19    /// Loads a list of content servers.
20    pub async fn load(
21        configuration: &SteamConfiguration,
22    ) -> Result<Vec<Server>, SteamError> {
23        Self::load_core(configuration, None, None, CancellationToken::new()).await
24    }
25
26    /// Loads a list of content servers, cancellable.
27    pub async fn load_with_cancellation(
28        configuration: &SteamConfiguration,
29        cancellation: CancellationToken,
30    ) -> Result<Vec<Server>, SteamError> {
31        Self::load_core(configuration, None, None, cancellation).await
32    }
33
34    /// Loads a list of content servers; `cell_id` is the preferred cell id.
35    pub async fn load_with_cell_id(
36        configuration: &SteamConfiguration,
37        cell_id: u32,
38        cancellation: CancellationToken,
39    ) -> Result<Vec<Server>, SteamError> {
40        Self::load_core(configuration, Some(cell_id), None, cancellation).await
41    }
42
43    /// Loads a list of content servers. `cell_id` is the preferred cell id and
44    /// `max_num_servers` caps the result.
45    pub async fn load_with_cell_id_and_max_num_servers(
46        configuration: &SteamConfiguration,
47        cell_id: u32,
48        max_num_servers: u32,
49        cancellation: CancellationToken,
50    ) -> Result<Vec<Server>, SteamError> {
51        Self::load_core(
52            configuration,
53            Some(cell_id),
54            Some(max_num_servers),
55            cancellation,
56        )
57        .await
58    }
59
60    /// The actual query.
61    async fn load_core(
62        configuration: &SteamConfiguration,
63        cell_id: Option<u32>,
64        max_num_servers: Option<u32>,
65        cancellation: CancellationToken,
66    ) -> Result<Vec<Server>, SteamError> {
67        let directory = get_async_web_api_interface(configuration, "IContentServerDirectoryService");
68
69        let mut request = CContentServerDirectoryGetServersForSteamPipeRequest {
70            cell_id: Some(cell_id.unwrap_or_else(|| configuration.cell_id())),
71            ..Default::default()
72        };
73
74        if let Some(max_num_servers) = max_num_servers {
75            request.max_servers = Some(max_num_servers);
76        }
77
78        if cancellation.is_cancelled() {
79            return Err(SteamError::Cancelled);
80        }
81
82        let response = directory
83            .call_protobuf_with_request::<
84                CContentServerDirectoryGetServersForSteamPipeResponse,
85                CContentServerDirectoryGetServersForSteamPipeRequest,
86            >(
87                reqwest::Method::GET,
88                "GetServersForSteamPipe",
89                &request,
90                1,
91                &[],
92            )
93            .await?;
94
95        if cancellation.is_cancelled() {
96            return Err(SteamError::Cancelled);
97        }
98
99        Ok(Self::convert_server_list(&response.body))
100    }
101
102    /// Converts the protobuf response to CDN server records.
103    pub fn convert_server_list(
104        response: &CContentServerDirectoryGetServersForSteamPipeResponse,
105    ) -> Vec<Server> {
106        let mut server_records = Vec::with_capacity(response.servers.len());
107
108        for child in &response.servers {
109            let https_support = child.https_support.as_deref();
110            let protocol = if https_support == Some("mandatory") {
111                ConnectionProtocol::Https
112            } else {
113                ConnectionProtocol::Http
114            };
115
116            server_records.push(Server {
117                protocol,
118                host: child.host.clone(),
119                v_host: child.vhost.clone(),
120                port: if protocol == ConnectionProtocol::Https {
121                    443
122                } else {
123                    80
124                },
125                server_type: child.r#type.clone(),
126                source_id: child.source_id.unwrap_or_default(),
127                cell_id: child.cell_id.unwrap_or_default() as u32,
128                load: child.load.unwrap_or_default(),
129                weighted_load: child.weighted_load.unwrap_or_default(),
130                num_entries: child.num_entries_in_client_list.unwrap_or_default(),
131                steam_china_only: child.steam_china_only.unwrap_or_default(),
132                use_as_proxy: child.use_as_proxy.unwrap_or_default(),
133                proxy_request_path_template: child.proxy_request_path_template.clone(),
134                allowed_app_ids: child.allowed_app_ids.clone(),
135                bypass_proxies_of_type: child.bypass_proxies_of_type.clone(),
136            });
137        }
138
139        server_records
140    }
141}