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 = "none", shared)]
27pub enum ResolveSourceRequestResult {
28    NotFound,
29    Static(ResolvedVc<StaticContent>, ResolvedVc<HeaderList>),
30    HttpProxy(OperationVc<ProxyResult>),
31}
32
33#[turbo_tasks::function(operation)]
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)]
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)]
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)]
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)]
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_strongly_consistent()
92        .await?;
93    'routes: loop {
94        let mut sources_op = route_tree_get_operation(route_tree, current_asset_path.clone());
95        'sources: loop {
96            for &get_content in sources_op.read_strongly_consistent().await?.iter() {
97                let content_vary = get_content_source_content_vary_operation(get_content)
98                    .read_strongly_consistent()
99                    .await?;
100                let content_data =
101                    request_to_data(&request_overwrites, &request, &content_vary).await?;
102                let content_op = get_content_source_content_get_operation(
103                    get_content,
104                    current_asset_path.clone(),
105                    content_data,
106                );
107                match &*content_op.read_strongly_consistent().await? {
108                    ContentSourceContent::Rewrite(rewrite) => {
109                        let rewrite = rewrite.await?;
110                        // apply rewrite extras
111                        if let Some(headers) = &rewrite.response_headers {
112                            response_header_overwrites.extend(headers.await?.iter().cloned());
113                        }
114                        if let Some(headers) = &rewrite.request_headers {
115                            request_overwrites.headers.clear();
116                            for (name, value) in &*headers.await? {
117                                request_overwrites.headers.insert(
118                                    HyperHeaderName::try_from(name.as_str())?,
119                                    HyperHeaderValue::try_from(value.as_str())?,
120                                );
121                            }
122                        }
123                        // do the rewrite
124                        match &rewrite.ty {
125                            RewriteType::Location { path_and_query } => {
126                                let new_uri = Uri::try_from(path_and_query.as_str())?;
127                                let new_asset_path =
128                                    urlencoding::decode(&new_uri.path()[1..])?.into_owned();
129                                request_overwrites.uri = new_uri;
130                                current_asset_path = new_asset_path.into();
131                                continue 'routes;
132                            }
133                            RewriteType::ContentSource {
134                                source,
135                                path_and_query,
136                            } => {
137                                let new_uri = Uri::try_from(path_and_query.as_str())?;
138                                let new_asset_path =
139                                    urlencoding::decode(&new_uri.path()[1..])?.into_owned();
140                                request_overwrites.uri = new_uri;
141                                current_asset_path = new_asset_path.into();
142                                route_tree = content_source_get_routes_operation(*source)
143                                    .resolve_strongly_consistent()
144                                    .await?;
145                                continue 'routes;
146                            }
147                            RewriteType::Sources {
148                                sources: new_sources,
149                            } => {
150                                sources_op = *new_sources;
151                                continue 'sources;
152                            }
153                        }
154                    }
155                    ContentSourceContent::NotFound => {
156                        return Ok(ResolveSourceRequestResult::NotFound.cell());
157                    }
158                    ContentSourceContent::Static(static_content) => {
159                        return Ok(ResolveSourceRequestResult::Static(
160                            *static_content,
161                            HeaderList::new(response_header_overwrites)
162                                .to_resolved()
163                                .await?,
164                        )
165                        .cell());
166                    }
167                    ContentSourceContent::HttpProxy(proxy_result) => {
168                        return Ok(ResolveSourceRequestResult::HttpProxy(*proxy_result).cell());
169                    }
170                    ContentSourceContent::Next => continue,
171                }
172            }
173            break;
174        }
175        break;
176    }
177    Ok(ResolveSourceRequestResult::NotFound.cell())
178}
179
180static CACHE_BUSTER: AtomicU64 = AtomicU64::new(0);
181
182async fn request_to_data(
183    request: &SourceRequest,
184    original_request: &SourceRequest,
185    vary: &ContentSourceDataVary,
186) -> Result<ContentSourceData> {
187    let mut data = ContentSourceData::default();
188    if vary.method {
189        data.method = Some(request.method.clone().into());
190    }
191    if vary.url {
192        data.url = Some(request.uri.to_string().into());
193    }
194    if vary.original_url {
195        data.original_url = Some(original_request.uri.to_string().into());
196    }
197    if vary.body {
198        data.body = Some(request.body.clone().resolved_cell());
199    }
200    if vary.raw_query {
201        data.raw_query = Some(request.uri.query().unwrap_or("").into());
202    }
203    if vary.raw_headers {
204        data.raw_headers = Some(
205            request
206                .headers
207                .iter()
208                .map(|(name, value)| {
209                    Ok((name.to_string().into(), value.to_str()?.to_string().into()))
210                })
211                .collect::<Result<Vec<_>>>()?,
212        );
213    }
214    if let Some(filter) = vary.query.as_ref() {
215        if let Some(query) = request.uri.query() {
216            let mut query: Query = serde_qs::from_str(query)?;
217            query.filter_with(filter);
218            data.query = Some(query);
219        } else {
220            data.query = Some(Query::default())
221        }
222    }
223    if let Some(filter) = vary.headers.as_ref() {
224        let mut headers = Headers::default();
225        for (header_name, header_value) in request.headers.iter() {
226            if !filter.contains(header_name.as_str()) {
227                continue;
228            }
229            match headers.entry(header_name.to_string()) {
230                Entry::Vacant(e) => {
231                    if let Ok(s) = header_value.to_str() {
232                        e.insert(HeaderValue::SingleString(s.to_string()));
233                    } else {
234                        e.insert(HeaderValue::SingleBytes(header_value.as_bytes().to_vec()));
235                    }
236                }
237                Entry::Occupied(mut e) => {
238                    if let Ok(s) = header_value.to_str() {
239                        e.get_mut().extend_with_string(s.to_string());
240                    } else {
241                        e.get_mut()
242                            .extend_with_bytes(header_value.as_bytes().to_vec());
243                    }
244                }
245            }
246        }
247        data.headers = Some(headers);
248    }
249    if vary.cache_buster {
250        data.cache_buster = CACHE_BUSTER.fetch_add(1, Ordering::SeqCst);
251    }
252    Ok(data)
253}