turbopack_dev_server/source/
mod.rs

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