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