Skip to main content

orinium_browser/platform/network/
core.rs

1//! ネットワークコア
2//! HTTP通信とレスポンス処理を担当する。
3
4use super::{HostKey, HttpSender, NetworkConfig, NetworkError, SenderPool};
5
6use http_body_util::{BodyExt, Empty};
7use hyper::{
8    Method, Request, Uri,
9    body::{Bytes, Incoming},
10    client::conn,
11    http::uri::Scheme,
12};
13use hyper_util::rt::TokioIo;
14use rustls::{ClientConfig, RootCertStore};
15use rustls_native_certs::load_native_certs;
16use serde::{Deserialize, Serialize};
17use std::sync::Arc;
18use tokio::{net::TcpStream, runtime::Runtime, task::LocalSet};
19use tokio_rustls::TlsConnector;
20
21pub(super) struct AsyncNetworkCore {
22    local: LocalSet,
23    rt: Runtime,
24    inner: NetworkInner,
25}
26
27impl AsyncNetworkCore {
28    pub fn new() -> Self {
29        let rt = tokio::runtime::Builder::new_current_thread()
30            .enable_all()
31            .build()
32            .expect("failed to build tokio runtime");
33
34        let local = LocalSet::new();
35
36        Self {
37            rt,
38            local,
39            inner: NetworkInner::new(),
40        }
41    }
42
43    pub fn set_network_config(&mut self, config: NetworkConfig) {
44        self.inner.set_network_config(config)
45    }
46
47    /// UI スレッドなどから呼ばれる blocking API
48    pub fn fetch_blocking(&self, url: &str) -> Result<Response, NetworkError> {
49        // network スレッド内で完結させる
50        self.local
51            .block_on(&self.rt, async { self.inner.fetch_url(url).await })
52    }
53}
54
55#[derive(Deserialize, Serialize)]
56pub struct StatusCode(u16);
57
58impl From<hyper::StatusCode> for StatusCode {
59    fn from(value: hyper::StatusCode) -> Self {
60        Self(value.as_u16())
61    }
62}
63
64impl StatusCode {
65    pub fn as_u16(&self) -> u16 {
66        self.0
67    }
68
69    pub fn is_success(&self) -> bool {
70        (200..300).contains(&self.0)
71    }
72
73    pub fn is_redirection(&self) -> bool {
74        (300..400).contains(&self.0)
75    }
76
77    pub fn canonical_reason(&self) -> Option<&'static str> {
78        let hyper_code: hyper::StatusCode = self.as_u16().try_into().ok()?;
79        hyper_code.canonical_reason()
80    }
81}
82
83/// HTTP response
84#[derive(Deserialize, Serialize)]
85pub struct Response {
86    pub url: String,
87    pub status: StatusCode,
88    pub reason_phrase: String,
89    pub headers: Vec<(String, String)>,
90    pub body: Vec<u8>,
91}
92
93pub(super) struct NetworkInner {
94    sender_pool: Arc<std::sync::RwLock<SenderPool>>,
95    tls_config: Arc<ClientConfig>,
96    network_config: Arc<NetworkConfig>,
97}
98
99impl NetworkInner {
100    pub fn new() -> Self {
101        Self {
102            sender_pool: Arc::new(std::sync::RwLock::new(SenderPool::new())),
103            tls_config: Arc::new(Self::build_tls_config()),
104            network_config: Arc::new(NetworkConfig::default()),
105        }
106    }
107
108    pub fn set_network_config(&mut self, confing: NetworkConfig) {
109        self.network_config = Arc::new(confing)
110    }
111
112    fn build_tls_config() -> ClientConfig {
113        let mut roots = RootCertStore::empty();
114        let result = load_native_certs();
115
116        for cert in result.certs {
117            let _ = roots.add(cert);
118        }
119
120        ClientConfig::builder()
121            .with_root_certificates(roots)
122            .with_no_client_auth()
123    }
124
125    pub async fn fetch_url(&self, url: &str) -> Result<Response, NetworkError> {
126        let mut current: Uri = url.parse().map_err(|_| NetworkError::InvalidUri)?;
127        let mut redirects = 0usize;
128
129        loop {
130            let resp = self.send_request(&current).await?;
131
132            if self.network_config.follow_redirects
133                && hyper::StatusCode::try_from(resp.status.0)
134                    .map_err(|_| NetworkError::InvalidIpcStatusCode)?
135                    .is_redirection()
136            {
137                if redirects >= 10 {
138                    return Err(NetworkError::TooManyRedirects);
139                }
140
141                if let Some(loc) = resp
142                    .headers
143                    .iter()
144                    .find(|(k, _)| k.eq_ignore_ascii_case("location"))
145                    .map(|(_, v)| v)
146                {
147                    current = resolve_redirect(&current, loc)?;
148                    redirects += 1;
149                    continue;
150                }
151            }
152
153            return Ok(resp);
154        }
155    }
156
157    async fn send_request(&self, uri: &Uri) -> Result<Response, NetworkError> {
158        let host = uri.host().ok_or(NetworkError::MissingHost)?;
159        let scheme = uri.scheme().unwrap_or(&Scheme::HTTP);
160        let port = uri
161            .port_u16()
162            .unwrap_or(if scheme == &Scheme::HTTPS { 443 } else { 80 });
163
164        let key = HostKey {
165            scheme: scheme.clone(),
166            host: host.to_string(),
167            port,
168        };
169
170        let mut sender = self.get_or_create_sender(&key).await?;
171
172        let req = Request::builder()
173            .method(Method::GET)
174            .uri(uri.path_and_query().map_or("/", |p| p.as_str()))
175            .header("Host", host)
176            .header("User-Agent", self.network_config.user_agent.as_str())
177            .body(Empty::<Bytes>::new())
178            .map_err(|_| NetworkError::HttpRequestFailed)?;
179
180        let mut res = match &mut sender {
181            HttpSender::Http1(s) => s
182                .send_request(req)
183                .await
184                .map_err(|_| NetworkError::HttpRequestFailed)?,
185            _ => {
186                return Err(NetworkError::UnsupportedHttpVersion);
187            }
188        };
189
190        let response = Self::collect_response(uri.to_string(), &mut res).await?;
191
192        self.sender_pool
193            .write()
194            .unwrap()
195            .add_connection(key, sender);
196
197        Ok(response)
198    }
199
200    async fn collect_response(
201        url: String,
202        res: &mut hyper::Response<Incoming>,
203    ) -> Result<Response, NetworkError> {
204        let status = res.status();
205        let reason_phrase = status.canonical_reason().unwrap_or("").to_string();
206
207        let headers = res
208            .headers()
209            .iter()
210            .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
211            .collect();
212
213        let mut body = Vec::new();
214        while let Some(frame) = res.frame().await {
215            let frame = frame.map_err(|_| NetworkError::HttpResponseFailed)?;
216            if let Some(chunk) = frame.data_ref() {
217                body.extend_from_slice(chunk);
218            }
219        }
220
221        Ok(Response {
222            url,
223            status: status.into(),
224            reason_phrase,
225            headers,
226            body,
227        })
228    }
229
230    async fn get_or_create_sender(&self, key: &HostKey) -> Result<HttpSender, NetworkError> {
231        if let Some(s) = self.sender_pool.write().unwrap().get_connection(key) {
232            return Ok(s);
233        }
234
235        self.create_connection(key).await
236    }
237
238    async fn create_connection(&self, key: &HostKey) -> Result<HttpSender, NetworkError> {
239        let addr = format!("{}:{}", key.host, key.port);
240        let stream = TcpStream::connect(addr)
241            .await
242            .map_err(|_| NetworkError::ConnectionFailed)?;
243
244        if key.scheme == Scheme::HTTPS {
245            let tls = TlsConnector::from(Arc::clone(&self.tls_config));
246            let key = key.clone();
247            let domain = rustls::pki_types::ServerName::try_from(key.host.clone())
248                .map_err(|_| NetworkError::InvalidDnsName)?;
249
250            let stream = tls
251                .connect(domain, stream)
252                .await
253                .map_err(|_| NetworkError::TlsFailed)?;
254
255            let (sender, conn) = conn::http1::handshake(TokioIo::new(stream))
256                .await
257                .map_err(|_| NetworkError::HttpHandshakeFailed)?;
258
259            self.spawn_connection_task(conn, key);
260            Ok(HttpSender::Http1(sender))
261        } else {
262            let (sender, conn) = conn::http1::handshake(TokioIo::new(stream))
263                .await
264                .map_err(|_| NetworkError::HttpHandshakeFailed)?;
265
266            self.spawn_connection_task(conn, key.clone());
267            Ok(HttpSender::Http1(sender))
268        }
269    }
270
271    fn spawn_connection_task(
272        &self,
273        conn: conn::http1::Connection<
274            TokioIo<impl tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + 'static>,
275            Empty<Bytes>,
276        >,
277        key: HostKey,
278    ) {
279        let pool = Arc::clone(&self.sender_pool);
280        tokio::task::spawn_local(async move {
281            let _ = conn.await;
282            pool.write().unwrap().remove_connection(&key);
283        });
284    }
285}
286
287fn resolve_redirect(base: &Uri, location: &str) -> Result<Uri, NetworkError> {
288    if location.starts_with("http://") || location.starts_with("https://") {
289        return location.parse().map_err(|_| NetworkError::InvalidUri);
290    }
291
292    let scheme = base.scheme_str().unwrap_or("https");
293    let authority = base.authority().ok_or(NetworkError::InvalidUri)?;
294
295    let next = if location.starts_with("//") {
296        format!("{scheme}:{location}")
297    } else if location.starts_with('/') {
298        format!("{scheme}://{}{location}", authority)
299    } else {
300        let base_path = base.path();
301        let prefix = base_path.rsplit_once('/').map_or("", |x| x.0);
302        format!("{scheme}://{}{prefix}/{location}", authority)
303    };
304
305    next.parse().map_err(|_| NetworkError::InvalidUri)
306}