Skip to main content

turbopack_dev_server/source/
resolve.rs

1use std::{
2    collections::btree_map::Entry,
3    sync::atomic::{AtomicU64, Ordering},
4};
5
6use anyhow::Result;
7use hyper::{
8    Uri,
9    header::{HeaderName as HyperHeaderName, HeaderValue as HyperHeaderValue},
10};
11use turbo_rcstr::RcStr;
12use turbo_tasks::{OperationVc, ResolvedVc, TransientInstance, Vc};
13
14use crate::source::{
15    ContentSource, ContentSourceContent, ContentSourceData, ContentSourceDataVary,
16    GetContentSourceContent, GetContentSourceContents, HeaderList, ProxyResult, RewriteType,
17    StaticContent,
18    headers::{HeaderValue, Headers},
19    query::Query,
20    request::SourceRequest,
21    route_tree::RouteTree,
22};
23
24/// The result of [`resolve_source_request`]. Similar to a [`ContentSourceContent`], but without the
25/// [`Rewrite`][ContentSourceContent::Rewrite] variant, as this is taken care in the function.
26#[turbo_tasks::value(serialization = "skip", shared)]
27pub enum ResolveSourceRequestResult {
28    NotFound,
29    Static(ResolvedVc<StaticContent>, ResolvedVc<HeaderList>),
30    HttpProxy(OperationVc<ProxyResult>),
31}
32
33#[turbo_tasks::function(operation, root)]
34fn content_source_get_routes_operation(
35    source: OperationVc<Box<dyn ContentSource>>,
36) -> Vc<RouteTree> {
37    source.connect().get_routes()
38}
39
40#[turbo_tasks::function(operation, root)]
41fn route_tree_get_operation(
42    route_tree: ResolvedVc<RouteTree>,
43    asset_path: RcStr,
44) -> Vc<GetContentSourceContents> {
45    route_tree.get(asset_path)
46}
47
48#[turbo_tasks::function(operation, root)]
49fn get_content_source_content_vary_operation(
50    get_content: ResolvedVc<Box<dyn GetContentSourceContent>>,
51) -> Vc<ContentSourceDataVary> {
52    get_content.vary()
53}
54
55#[turbo_tasks::function(operation, root)]
56fn get_content_source_content_get_operation(
57    get_content: ResolvedVc<Box<dyn GetContentSourceContent>>,
58    path: RcStr,
59    data: ContentSourceData,
60) -> Vc<ContentSourceContent> {
61    get_content.get(path, data)
62}
63
64/// Resolves a [`SourceRequest`] within a [`ContentSource`], returning the corresponding content.
65///
66/// Matches the first [`ContentSourceContent`] in the [`RouteTree`] returned by
67/// [`ContentSource::get_routes`] that does not generate [`ContentSourceContent::Next`].
68///
69/// *In the future*, this function may be used at the boundary of consistency. All invoked methods
70/// should be read using [strong consistency][OperationVc::read_strongly_consistent]. This ensures
71/// that all requests serve the latest version of the content.
72///
73/// If this function is not called/read with strong consistency, [`ContentSource::get_routes`] would
74/// be allowed to be independently consistent. Side effects should not need to wait for
75/// recomputation of [`ContentSource::get_routes`].
76///
77/// TODO: The callers of this function now read this operation using strong consistency. This may
78/// have re-introduced performance issues that were solved in
79/// <https://github.com/vercel/turborepo/pull/5360>.
80#[turbo_tasks::function(operation, root)]
81pub async fn resolve_source_request(
82    source: OperationVc<Box<dyn ContentSource>>,
83    request: TransientInstance<SourceRequest>,
84) -> Result<Vc<ResolveSourceRequestResult>> {
85    let original_path = request.uri.path().to_string();
86    // Remove leading slash.
87    let mut current_asset_path: RcStr = urlencoding::decode(&original_path[1..])?.into();
88    let mut request_overwrites = (*request).clone();
89    let mut response_header_overwrites = Vec::new();
90    let mut route_tree = content_source_get_routes_operation(source)
91        .resolve()
92        .strongly_consistent()
93        .await?;
94    'routes: loop {
95        let mut sources_op = route_tree_get_operation(route_tree, current_asset_path.clone());
96        'sources: loop {
97            for &get_content in sources_op.read_strongly_consistent().await?.iter() {
98                let content_vary = get_content_source_content_vary_operation(get_content)
99                    .read_strongly_consistent()
100                    .await?;
101                let content_data =
102                    request_to_data(&request_overwrites, &request, &content_vary).await?;
103                let content_op = get_content_source_content_get_operation(
104                    get_content,
105                    current_asset_path.clone(),
106                    content_data,
107                );
108                match &*content_op.read_strongly_consistent().await? {
109                    ContentSourceContent::Rewrite(rewrite) => {
110                        let rewrite = rewrite.await?;
111                        // apply rewrite extras
112                        if let Some(headers) = &rewrite.response_headers {
113                            response_header_overwrites.extend(headers.await?.iter().cloned());
114                        }
115                        if let Some(headers) = &rewrite.request_headers {
116                            request_overwrites.headers.clear();
117                            for (name, value) in &*headers.await? {
118                                request_overwrites.headers.insert(
119                                    HyperHeaderName::try_from(name.as_str())?,
120                                    HyperHeaderValue::try_from(value.as_str())?,
121                                );
122                            }
123                        }
124                        // do the rewrite
125                        match &rewrite.ty {
126                            RewriteType::Location { path_and_query } => {
127                                let new_uri = Uri::try_from(path_and_query.as_str())?;
128                                let new_asset_path =
129                                    urlencoding::decode(&new_uri.path()[1..])?.into_owned();
130                                request_overwrites.uri = new_uri;
131                                current_asset_path = new_asset_path.into();
132                                continue 'routes;
133                            }
134                            RewriteType::ContentSource {
135                                source,
136                                path_and_query,
137                            } => {
138                                let new_uri = Uri::try_from(path_and_query.as_str())?;
139                                let new_asset_path =
140                                    urlencoding::decode(&new_uri.path()[1..])?.into_owned();
141                                request_overwrites.uri = new_uri;
142                                current_asset_path = new_asset_path.into();
143                                route_tree = content_source_get_routes_operation(*source)
144                                    .resolve()
145                                    .strongly_consistent()
146                                    .await?;
147                                continue 'routes;
148                            }
149                            RewriteType::Sources {
150                                sources: new_sources,
151                            } => {
152                                sources_op = *new_sources;
153                                continue 'sources;
154                            }
155                        }
156                    }
157                    ContentSourceContent::NotFound => {
158                        return Ok(ResolveSourceRequestResult::NotFound.cell());
159                    }
160                    ContentSourceContent::Static(static_content) => {
161                        return Ok(ResolveSourceRequestResult::Static(
162                            *static_content,
163                            HeaderList::new(response_header_overwrites)
164                                .to_resolved()
165                                .await?,
166                        )
167                        .cell());
168                    }
169                    ContentSourceContent::HttpProxy(proxy_result) => {
170                        return Ok(ResolveSourceRequestResult::HttpProxy(*proxy_result).cell());
171                    }
172                    ContentSourceContent::Next => continue,
173                }
174            }
175            break;
176        }
177        break;
178    }
179    Ok(ResolveSourceRequestResult::NotFound.cell())
180}
181
182static CACHE_BUSTER: AtomicU64 = AtomicU64::new(0);
183
184async fn request_to_data(
185    request: &SourceRequest,
186    original_request: &SourceRequest,
187    vary: &ContentSourceDataVary,
188) -> Result<ContentSourceData> {
189    let mut data = ContentSourceData::default();
190    if vary.method {
191        data.method = Some(request.method.clone().into());
192    }
193    if vary.url {
194        data.url = Some(request.uri.to_string().into());
195    }
196    if vary.original_url {
197        data.original_url = Some(original_request.uri.to_string().into());
198    }
199    if vary.body {
200        data.body = Some(request.body.clone().resolved_cell());
201    }
202    if vary.raw_query {
203        data.raw_query = Some(request.uri.query().unwrap_or("").into());
204    }
205    if vary.raw_headers {
206        data.raw_headers = Some(
207            request
208                .headers
209                .iter()
210                .map(|(name, value)| {
211                    Ok((name.to_string().into(), value.to_str()?.to_string().into()))
212                })
213                .collect::<Result<Vec<_>>>()?,
214        );
215    }
216    if let Some(filter) = vary.query.as_ref() {
217        if let Some(query) = request.uri.query() {
218            let mut query: Query = serde_qs::from_str(query)?;
219            query.filter_with(filter);
220            data.query = Some(query);
221        } else {
222            data.query = Some(Query::default())
223        }
224    }
225    if let Some(filter) = vary.headers.as_ref() {
226        let mut headers = Headers::default();
227        for (header_name, header_value) in request.headers.iter() {
228            if !filter.contains(header_name.as_str()) {
229                continue;
230            }
231            match headers.entry(header_name.to_string()) {
232                Entry::Vacant(e) => {
233                    if let Ok(s) = header_value.to_str() {
234                        e.insert(HeaderValue::SingleString(s.to_string()));
235                    } else {
236                        e.insert(HeaderValue::SingleBytes(header_value.as_bytes().to_vec()));
237                    }
238                }
239                Entry::Occupied(mut e) => {
240                    if let Ok(s) = header_value.to_str() {
241                        e.get_mut().extend_with_string(s.to_string());
242                    } else {
243                        e.get_mut()
244                            .extend_with_bytes(header_value.as_bytes().to_vec());
245                    }
246                }
247            }
248        }
249        data.headers = Some(headers);
250    }
251    if vary.cache_buster {
252        data.cache_buster = CACHE_BUSTER.fetch_add(1, Ordering::SeqCst);
253    }
254    Ok(data)
255}