Skip to main content

turbopack_dev_server/source/
mod.rs

1pub mod asset_graph;
2pub mod combined;
3pub mod headers;
4pub mod issue_context;
5pub mod lazy_instantiated;
6pub mod query;
7pub mod request;
8pub mod resolve;
9pub mod route_tree;
10pub mod router;
11pub mod static_assets;
12pub mod wrapping_source;
13
14use std::collections::BTreeSet;
15
16use anyhow::Result;
17use bincode::{Decode, Encode};
18use futures::{TryStreamExt, stream::Stream as StreamTrait};
19use turbo_rcstr::RcStr;
20use turbo_tasks::{
21    Completion, NonLocalValue, OperationVc, ResolvedVc, Upcast, Vc, util::SharedError,
22};
23use turbo_tasks_bytes::{Bytes, Stream, StreamRead};
24use turbo_tasks_fs::FileSystemPath;
25use turbo_tasks_hash::{DeterministicHash, DeterministicHasher, Xxh3Hash64Hasher};
26use turbopack_core::version::{Version, VersionedContent};
27
28use crate::source::{
29    headers::Headers, issue_context::IssueFilePathContentSource, query::Query,
30    route_tree::RouteTree,
31};
32
33/// The result of proxying a request to another HTTP server.
34#[turbo_tasks::value(shared, operation)]
35pub struct ProxyResult {
36    /// The HTTP status code to return.
37    pub status: u16,
38    /// Headers arranged as contiguous (name, value) pairs.
39    pub headers: Vec<(RcStr, RcStr)>,
40    /// The body to return.
41    #[turbo_tasks(unsafe_ignore)]
42    pub body: Body,
43}
44
45#[turbo_tasks::value_impl]
46impl Version for ProxyResult {
47    #[turbo_tasks::function]
48    async fn id(&self) -> Result<Vc<RcStr>> {
49        let mut hash = Xxh3Hash64Hasher::new();
50        hash.write_u16(self.status);
51        for (name, value) in &self.headers {
52            name.deterministic_hash(&mut hash);
53            value.deterministic_hash(&mut hash);
54        }
55        let mut read = self.body.read();
56        while let Some(chunk) = read.try_next().await? {
57            hash.write_bytes(&chunk);
58        }
59        Ok(Vc::cell(hash.finish().to_string().into()))
60    }
61}
62
63/// Receives the actual content for a [`ContentSource`].
64#[turbo_tasks::value_trait]
65pub trait GetContentSourceContent {
66    /// Specifies data requirements for the [`get`][Self::get] function. Restricting data passed
67    /// allows improved caching of the [`get`][Self::get] method.
68    #[turbo_tasks::function]
69    fn vary(self: Vc<Self>) -> Vc<ContentSourceDataVary> {
70        ContentSourceDataVary::default().cell()
71    }
72
73    #[turbo_tasks::function]
74    fn get(self: Vc<Self>, path: RcStr, data: ContentSourceData) -> Vc<ContentSourceContent>;
75}
76
77#[turbo_tasks::value(transparent)]
78pub struct GetContentSourceContents(Vec<ResolvedVc<Box<dyn GetContentSourceContent>>>);
79
80#[turbo_tasks::value]
81pub struct StaticContent {
82    pub content: ResolvedVc<Box<dyn VersionedContent>>,
83    pub status_code: u16,
84    pub headers: ResolvedVc<HeaderList>,
85}
86
87#[turbo_tasks::value(shared)]
88/// The content of a result that is returned by [`GetContentSourceContent::get`].
89// TODO: add a `Dynamic` variant in future to allow streaming and server responses
90pub enum ContentSourceContent {
91    NotFound,
92    Static(ResolvedVc<StaticContent>),
93    HttpProxy(OperationVc<ProxyResult>),
94    Rewrite(ResolvedVc<Rewrite>),
95    /// Continue with the next route
96    Next,
97}
98
99/// This trait can be emitted as collectible and will be applied after the
100/// request is handled and it's ensured that it finishes before the next request
101/// is handled.
102#[turbo_tasks::value_trait]
103pub trait ContentSourceSideEffect {
104    #[turbo_tasks::function]
105    fn apply(self: Vc<Self>) -> Vc<Completion>;
106}
107
108#[turbo_tasks::value_impl]
109impl GetContentSourceContent for ContentSourceContent {
110    #[turbo_tasks::function]
111    fn get(self: Vc<Self>, _path: RcStr, _data: ContentSourceData) -> Vc<ContentSourceContent> {
112        self
113    }
114}
115
116#[turbo_tasks::value_impl]
117impl ContentSourceContent {
118    #[turbo_tasks::function]
119    pub async fn static_content(
120        content: ResolvedVc<Box<dyn VersionedContent>>,
121    ) -> Result<Vc<ContentSourceContent>> {
122        Ok(ContentSourceContent::Static(
123            StaticContent {
124                content,
125                status_code: 200,
126                headers: HeaderList::empty().to_resolved().await?,
127            }
128            .resolved_cell(),
129        )
130        .cell())
131    }
132
133    #[turbo_tasks::function]
134    pub fn not_found() -> Vc<ContentSourceContent> {
135        ContentSourceContent::NotFound.cell()
136    }
137}
138
139/// A list of headers arranged as contiguous (name, value) pairs.
140#[turbo_tasks::value(transparent)]
141pub struct HeaderList(Vec<(RcStr, RcStr)>);
142
143#[turbo_tasks::value_impl]
144impl HeaderList {
145    #[turbo_tasks::function]
146    pub fn new(headers: Vec<(RcStr, RcStr)>) -> Vc<Self> {
147        HeaderList(headers).cell()
148    }
149
150    #[turbo_tasks::function]
151    pub fn empty() -> Vc<Self> {
152        HeaderList(vec![]).cell()
153    }
154}
155
156/// Additional info passed to the [`ContentSource`]. It was extracted from the http request.
157///
158/// Note that you might not receive information that has not been requested via
159/// [`GetContentSourceContent::vary`]. So make sure to request all information that's needed.
160#[turbo_tasks::task_input]
161#[derive(PartialEq, Eq, Clone, Debug, Hash, Default, Encode, Decode)]
162pub struct ContentSourceData {
163    /// HTTP method, if requested.
164    pub method: Option<RcStr>,
165    /// The full url (including query string), if requested.
166    pub url: Option<RcStr>,
167    /// The full url (including query string) before rewrites where applied, if
168    /// requested.
169    pub original_url: Option<RcStr>,
170    /// Query string items, if requested.
171    pub query: Option<Query>,
172    /// raw query string, if requested. Does not include the `?`.
173    pub raw_query: Option<RcStr>,
174    /// HTTP headers, might contain multiple headers with the same name, if
175    /// requested.
176    pub headers: Option<Headers>,
177    /// Raw HTTP headers, might contain multiple headers with the same name, if
178    /// requested.
179    pub raw_headers: Option<Vec<(RcStr, RcStr)>>,
180    /// Request body, if requested.
181    pub body: Option<ResolvedVc<Body>>,
182    /// See [ContentSourceDataVary::cache_buster].
183    pub cache_buster: u64,
184}
185
186pub type BodyChunk = Result<Bytes, SharedError>;
187
188/// A request body.
189#[turbo_tasks::value(shared)]
190#[derive(Default, Clone, Debug)]
191pub struct Body {
192    #[turbo_tasks(unsafe_ignore)]
193    chunks: Stream<BodyChunk>,
194}
195
196impl Body {
197    /// Creates a new body from a list of chunks.
198    pub fn new(chunks: Vec<BodyChunk>) -> Self {
199        Self {
200            chunks: Stream::new_closed(chunks),
201        }
202    }
203
204    /// Returns an iterator over the body's chunks.
205    pub fn read(&self) -> StreamRead<BodyChunk> {
206        self.chunks.read()
207    }
208
209    pub fn from_stream<T: StreamTrait<Item = BodyChunk> + Send + Unpin + 'static>(
210        source: T,
211    ) -> Self {
212        Self {
213            chunks: Stream::from(source),
214        }
215    }
216}
217
218impl<T: Into<Bytes>> From<T> for Body {
219    fn from(value: T) -> Self {
220        Body::new(vec![Ok(value.into())])
221    }
222}
223
224/// Filter function that describes which information is required.
225#[derive(Debug, Clone, PartialEq, Eq, Hash, NonLocalValue, Encode, Decode)]
226pub enum ContentSourceDataFilter {
227    All,
228    Subset(BTreeSet<String>),
229}
230
231impl ContentSourceDataFilter {
232    /// Merges the filtering to get a filter that covers both filters.
233    pub fn extend(&mut self, other: &ContentSourceDataFilter) {
234        match self {
235            ContentSourceDataFilter::All => {}
236            ContentSourceDataFilter::Subset(set) => match other {
237                ContentSourceDataFilter::All => *self = ContentSourceDataFilter::All,
238                ContentSourceDataFilter::Subset(set2) => set.extend(set2.iter().cloned()),
239            },
240        }
241    }
242
243    /// Merges the filtering to get a filter that covers both filters. Works on
244    /// [`Option<ContentSourceDataFilter>`][ContentSourceDataFilter] where [`None`] behaves as a
245    /// no-op empty filter.
246    pub fn extend_options(
247        this: &mut Option<ContentSourceDataFilter>,
248        other: &Option<ContentSourceDataFilter>,
249    ) {
250        if let Some(this) = this.as_mut() {
251            if let Some(other) = other.as_ref() {
252                this.extend(other);
253            }
254        } else {
255            this.clone_from(other);
256        }
257    }
258
259    /// Returns true if the filter contains the given key.
260    pub fn contains(&self, key: &str) -> bool {
261        match self {
262            ContentSourceDataFilter::All => true,
263            ContentSourceDataFilter::Subset(set) => set.contains(key),
264        }
265    }
266
267    /// Returns true if the first argument at least contains all values that the
268    /// second argument would contain.
269    pub fn fulfills(
270        this: &Option<ContentSourceDataFilter>,
271        other: &Option<ContentSourceDataFilter>,
272    ) -> bool {
273        match (this, other) {
274            (_, None) => true,
275            (None, Some(_)) => false,
276            (Some(this), Some(other)) => match (this, other) {
277                (ContentSourceDataFilter::All, _) => true,
278                (_, ContentSourceDataFilter::All) => false,
279                (ContentSourceDataFilter::Subset(this), ContentSourceDataFilter::Subset(other)) => {
280                    this.is_superset(other)
281                }
282            },
283        }
284    }
285}
286
287/// Describes additional information that need to be sent to requests to [`ContentSource`]. By
288/// sending these information [`ContentSource`] responses are cached-keyed by them and they can
289/// access them.
290#[turbo_tasks::value(shared)]
291#[derive(Debug, Default, Clone, Hash)]
292pub struct ContentSourceDataVary {
293    pub method: bool,
294    pub url: bool,
295    pub original_url: bool,
296    pub query: Option<ContentSourceDataFilter>,
297    pub raw_query: bool,
298    pub headers: Option<ContentSourceDataFilter>,
299    pub raw_headers: bool,
300    pub body: bool,
301    /// When true, a `cache_buster` value is added to the [ContentSourceData].
302    /// This value will be different on every request, which ensures the
303    /// content is never cached.
304    pub cache_buster: bool,
305    pub placeholder_for_future_extensions: (),
306}
307
308impl ContentSourceDataVary {
309    /// Merges two vary specification to create a combination of both that cover all information
310    /// requested by either one
311    pub fn extend(&mut self, other: &ContentSourceDataVary) {
312        let ContentSourceDataVary {
313            method,
314            url,
315            original_url,
316            query,
317            raw_query,
318            headers,
319            raw_headers,
320            body,
321            cache_buster,
322            placeholder_for_future_extensions: _,
323        } = self;
324        *method = *method || other.method;
325        *url = *url || other.url;
326        *original_url = *original_url || other.original_url;
327        *body = *body || other.body;
328        *cache_buster = *cache_buster || other.cache_buster;
329        *raw_query = *raw_query || other.raw_query;
330        *raw_headers = *raw_headers || other.raw_headers;
331        ContentSourceDataFilter::extend_options(query, &other.query);
332        ContentSourceDataFilter::extend_options(headers, &other.headers);
333    }
334
335    /// Returns true if `self` at least contains all values that the argument would contain.
336    pub fn fulfills(&self, other: &ContentSourceDataVary) -> bool {
337        // All fields must be used!
338        let ContentSourceDataVary {
339            method,
340            url,
341            original_url,
342            query,
343            raw_query,
344            headers,
345            raw_headers,
346            body,
347            cache_buster,
348            placeholder_for_future_extensions: _,
349        } = self;
350        if other.method && !method {
351            return false;
352        }
353        if other.url && !url {
354            return false;
355        }
356        if other.original_url && !original_url {
357            return false;
358        }
359        if other.body && !body {
360            return false;
361        }
362        if other.raw_query && !raw_query {
363            return false;
364        }
365        if other.raw_headers && !raw_headers {
366            return false;
367        }
368        if other.cache_buster && !cache_buster {
369            return false;
370        }
371        if !ContentSourceDataFilter::fulfills(query, &other.query) {
372            return false;
373        }
374        if !ContentSourceDataFilter::fulfills(headers, &other.headers) {
375            return false;
376        }
377        true
378    }
379}
380
381/// A source of content that the dev server uses to respond to http requests.
382#[turbo_tasks::value_trait]
383pub trait ContentSource {
384    #[turbo_tasks::function]
385    fn get_routes(self: Vc<Self>) -> Vc<RouteTree>;
386
387    /// Gets any content sources wrapped in this content source.
388    #[turbo_tasks::function]
389    fn get_children(self: Vc<Self>) -> Vc<ContentSources> {
390        ContentSources::empty()
391    }
392}
393
394pub trait ContentSourceExt {
395    fn issue_file_path(
396        self: Vc<Self>,
397        file_path: FileSystemPath,
398        description: RcStr,
399    ) -> Vc<Box<dyn ContentSource>>;
400}
401
402impl<T> ContentSourceExt for T
403where
404    T: Upcast<Box<dyn ContentSource>>,
405{
406    fn issue_file_path(
407        self: Vc<Self>,
408        file_path: FileSystemPath,
409        description: RcStr,
410    ) -> Vc<Box<dyn ContentSource>> {
411        Vc::upcast(IssueFilePathContentSource::new_file_path(
412            file_path,
413            description,
414            Vc::upcast_non_strict(self),
415        ))
416    }
417}
418
419#[turbo_tasks::value(transparent)]
420pub struct ContentSources(Vec<ResolvedVc<Box<dyn ContentSource>>>);
421
422#[turbo_tasks::value_impl]
423impl ContentSources {
424    #[turbo_tasks::function]
425    pub fn empty() -> Vc<Self> {
426        Vc::cell(Vec::new())
427    }
428}
429
430/// An empty ContentSource implementation that responds with NotFound for every
431/// request.
432#[turbo_tasks::value]
433pub struct NoContentSource;
434
435#[turbo_tasks::value_impl]
436impl NoContentSource {
437    #[turbo_tasks::function]
438    pub fn new() -> Vc<Self> {
439        NoContentSource.cell()
440    }
441}
442#[turbo_tasks::value_impl]
443impl ContentSource for NoContentSource {
444    #[turbo_tasks::function]
445    fn get_routes(&self) -> Vc<RouteTree> {
446        RouteTree::empty()
447    }
448}
449
450#[derive(Debug, Clone, PartialEq, Eq, NonLocalValue, Encode, Decode)]
451pub enum RewriteType {
452    Location {
453        /// The new path and query used to lookup content. This _does not_ need to be the original
454        /// path or query.
455        path_and_query: RcStr,
456    },
457    ContentSource {
458        /// [`ContentSource`]s from which to restart the lookup process. This _does not_ need to be
459        /// the original content source.
460        source: OperationVc<Box<dyn ContentSource>>,
461        /// The new path and query used to lookup content. This _does not_ need
462        /// to be the original path or query.
463        path_and_query: RcStr,
464    },
465    Sources {
466        /// [`GetContentSourceContent`]s from which to restart the lookup process. This _does not_
467        /// need to be the original content source.
468        sources: OperationVc<GetContentSourceContents>,
469    },
470}
471
472/// A rewrite returned from a [ContentSource]. This tells the dev server to
473/// update its parsed url, path, and queries with this new information.
474#[turbo_tasks::value(shared)]
475#[derive(Debug)]
476pub struct Rewrite {
477    pub ty: RewriteType,
478
479    /// A [Headers] which will be appended to the eventual, fully resolved
480    /// content result. This overwrites any previous matching headers.
481    pub response_headers: Option<ResolvedVc<HeaderList>>,
482
483    /// A [HeaderList] which will overwrite the values used during the lookup
484    /// process. All headers not present in this list will be deleted.
485    pub request_headers: Option<ResolvedVc<HeaderList>>,
486}
487
488pub struct RewriteBuilder {
489    rewrite: Rewrite,
490}
491
492impl RewriteBuilder {
493    pub fn new(path_and_query: RcStr) -> Self {
494        Self {
495            rewrite: Rewrite {
496                ty: RewriteType::Location { path_and_query },
497                response_headers: None,
498                request_headers: None,
499            },
500        }
501    }
502
503    pub fn new_source_with_path_and_query(
504        source: OperationVc<Box<dyn ContentSource>>,
505        path_and_query: RcStr,
506    ) -> Self {
507        Self {
508            rewrite: Rewrite {
509                ty: RewriteType::ContentSource {
510                    source,
511                    path_and_query,
512                },
513                response_headers: None,
514                request_headers: None,
515            },
516        }
517    }
518
519    pub fn new_sources(sources: OperationVc<GetContentSourceContents>) -> Self {
520        Self {
521            rewrite: Rewrite {
522                ty: RewriteType::Sources { sources },
523                response_headers: None,
524                request_headers: None,
525            },
526        }
527    }
528
529    /// Sets response headers to append to the eventual, fully resolved content
530    /// result.
531    pub fn response_headers(mut self, headers: ResolvedVc<HeaderList>) -> Self {
532        self.rewrite.response_headers = Some(headers);
533        self
534    }
535
536    /// Sets request headers to overwrite the headers used during the lookup
537    /// process.
538    pub fn request_headers(mut self, headers: ResolvedVc<HeaderList>) -> Self {
539        self.rewrite.request_headers = Some(headers);
540        self
541    }
542
543    pub fn build(self) -> Vc<Rewrite> {
544        self.rewrite.cell()
545    }
546}