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