Skip to content

Commit

Permalink
Implement Rewrite Headers support during routing (#3897)
Browse files Browse the repository at this point in the history
  • Loading branch information
jridgewell authored Feb 22, 2023
1 parent a996b40 commit c1ac2ee
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 36 deletions.
11 changes: 6 additions & 5 deletions crates/next-core/src/router_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use turbopack_core::{
};
use turbopack_dev_server::source::{
ContentSource, ContentSourceContent, ContentSourceData, ContentSourceDataVary,
ContentSourceResultVc, ContentSourceVc, NeededData, ProxyResult, RewriteVc,
ContentSourceResultVc, ContentSourceVc, HeaderListVc, NeededData, ProxyResult, RewriteBuilder,
};
use turbopack_node::execution_context::ExecutionContextVc;

Expand Down Expand Up @@ -110,11 +110,12 @@ impl ContentSource for NextRouterContentSource {
.inner
.get(path, Value::new(ContentSourceData::default())),
RouterResult::Rewrite(data) => {
// TODO: We can't set response headers on the returned content.
let mut rewrite = RewriteBuilder::new(data.url.clone()).content_source(this.inner);
if !data.headers.is_empty() {
rewrite = rewrite.response_headers(HeaderListVc::new(data.headers.clone()));
}
ContentSourceResultVc::exact(
ContentSourceContent::Rewrite(RewriteVc::new(data.url.clone(), this.inner))
.cell()
.into(),
ContentSourceContent::Rewrite(rewrite.build()).cell().into(),
)
}
RouterResult::FullMiddleware(data) => ContentSourceResultVc::exact(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export default function Home() {
}

function runTests() {
console.log(document.querySelectorAll("img"));
it("it should link to imported image", function () {
const img = document.querySelector("#imported");
expect(img.src).toContain(encodeURIComponent("_next/static/assets"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** @type {import('next').NextConfig} */
module.exports = {
async redirects() {
return [
{
source: "/foo",
destination: "https://example.vercel.sh/",
permanent: true,
},
];
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useEffect } from "react";

export default function Foo() {
useEffect(() => {
// Only run on client
import("@turbo/pack-test-harness").then(runTests);
});

return "index";
}

function runTests() {
it("it should display foo, not index", async () => {
const res = await fetch("/foo");
expect(res.url).toBe("https://example.vercel.sh/");
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/** @type {import('next').NextConfig} */
module.exports = {
async rewrites() {
return [
{
source: "/",
destination: "/foo",
},
];
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useEffect } from "react";

export default function Foo() {
useEffect(() => {
// Only run on client
import("@turbo/pack-test-harness").then(runTests);
});

return "foo";
}

function runTests() {
it("it should display foo, not index", () => {
expect(document.getElementById("__next").textContent).toBe("foo");
});
}
12 changes: 11 additions & 1 deletion crates/turbopack-dev-server/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ enum GetFromSourceResult {
content: FileContentReadRef,
status_code: u16,
headers: HeaderListReadRef,
header_overwrites: HeaderListReadRef,
},
HttpProxy(ProxyResultReadRef),
NotFound,
Expand All @@ -33,13 +34,14 @@ async fn get_from_source(
) -> Result<GetFromSourceResultVc> {
Ok(
match &*resolve_source_request(source, request, issue_repoter).await? {
ResolveSourceRequestResult::Static(static_content_vc) => {
ResolveSourceRequestResult::Static(static_content_vc, header_overwrites) => {
let static_content = static_content_vc.await?;
if let AssetContent::File(file) = &*static_content.content.content().await? {
GetFromSourceResult::Static {
content: file.await?,
status_code: static_content.status_code,
headers: static_content.headers.await?,
header_overwrites: header_overwrites.await?,
}
} else {
GetFromSourceResult::NotFound
Expand Down Expand Up @@ -69,6 +71,7 @@ pub async fn process_request_with_content_source(
content,
status_code,
headers,
header_overwrites,
} => {
if let FileContent::Content(file) = &**content {
let mut response = Response::builder().status(*status_code);
Expand All @@ -82,6 +85,13 @@ pub async fn process_request_with_content_source(
);
}

for (header_name, header_value) in header_overwrites.iter() {
header_map.insert(
HeaderName::try_from(header_name.clone())?,
hyper::header::HeaderValue::try_from(header_value)?,
);
}

if let Some(content_type) = file.content_type() {
header_map.append(
"content-type",
Expand Down
62 changes: 40 additions & 22 deletions crates/turbopack-dev-server/src/source/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,12 @@ pub struct HeaderList(Vec<(String, String)>);
#[turbo_tasks::value_impl]
impl HeaderListVc {
#[turbo_tasks::function]
pub fn empty() -> HeaderListVc {
pub fn new(headers: Vec<(String, String)>) -> Self {
HeaderList(headers).cell()
}

#[turbo_tasks::function]
pub fn empty() -> Self {
HeaderList(vec![]).cell()
}
}
Expand Down Expand Up @@ -497,32 +502,45 @@ pub struct Rewrite {

/// A [ContentSource] from which to restart the lookup process. This _does
/// not_ need to be the original content source. Having [None] source will
/// restart the lookup process from the original ContentSource.
/// restart the lookup process from the original root ContentSource.
pub source: Option<ContentSourceVc>,

/// A [Headers] which will be appended to the eventual, fully resolved
/// content result. This overwrites any previous matching headers.
pub response_headers: Option<HeaderListVc>,
}

#[turbo_tasks::value_impl]
impl RewriteVc {
/// Creates a new [RewriteVc] and starts lookup from the provided
/// [ContentSource].
#[turbo_tasks::function]
pub fn new(path_query: String, source: ContentSourceVc) -> RewriteVc {
debug_assert!(path_query.starts_with('/'));
Rewrite {
path_and_query: path_query,
source: Some(source),
pub struct RewriteBuilder {
rewrite: Rewrite,
}

impl RewriteBuilder {
pub fn new(path_and_query: String) -> Self {
Self {
rewrite: Rewrite {
path_and_query,
source: None,
response_headers: None,
},
}
.cell()
}

/// Creates a new [RewriteVc] and restarts lookup from the root.
#[turbo_tasks::function]
pub fn new_path_query(path_query: String) -> RewriteVc {
debug_assert!(path_query.starts_with('/'));
Rewrite {
path_and_query: path_query,
source: None,
}
.cell()
/// Sets the [ContentSource] from which to restart the lookup process.
/// Without a source, the lookup will restart from the original root
/// ContentSource.
pub fn content_source(mut self, source: ContentSourceVc) -> Self {
self.rewrite.source = Some(source);
self
}

/// Sets response headers to append to the eventual, fully resolved content
/// result.
pub fn response_headers(mut self, headers: HeaderListVc) -> Self {
self.rewrite.response_headers = Some(headers);
self
}

pub fn build(self) -> RewriteVc {
self.rewrite.cell()
}
}
14 changes: 11 additions & 3 deletions crates/turbopack-dev-server/src/source/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use super::{
query::Query,
request::SourceRequest,
ContentSourceContent, ContentSourceDataVary, ContentSourceResult, ContentSourceVc,
ProxyResultVc, StaticContentVc,
HeaderListVc, ProxyResultVc, StaticContentVc,
};
use crate::{
handle_issues,
Expand All @@ -26,7 +26,7 @@ use crate::{
#[turbo_tasks::value(serialization = "none")]
pub enum ResolveSourceRequestResult {
NotFound,
Static(StaticContentVc),
Static(StaticContentVc, HeaderListVc),
HttpProxy(ProxyResultVc),
}

Expand All @@ -44,6 +44,7 @@ pub async fn resolve_source_request(
let original_path = request.uri.path().to_string();
let mut current_asset_path = urlencoding::decode(&original_path[1..])?.into_owned();
let mut request_overwrites = (*request).clone();
let mut response_header_overwrites = Vec::new();
loop {
let result = current_source.get(&current_asset_path, Value::new(data));
handle_issues(
Expand Down Expand Up @@ -79,14 +80,21 @@ pub async fn resolve_source_request(

current_source = new_source.resolve().await?;
request_overwrites.uri = new_uri;
if let Some(headers) = &rewrite.response_headers {
response_header_overwrites.extend(headers.await?.iter().cloned());
}
current_asset_path = new_asset_path;
data = ContentSourceData::default();
} // _ => ,
ContentSourceContent::NotFound => {
break Ok(ResolveSourceRequestResult::NotFound.cell())
}
ContentSourceContent::Static(static_content) => {
break Ok(ResolveSourceRequestResult::Static(*static_content).cell())
break Ok(ResolveSourceRequestResult::Static(
*static_content,
HeaderListVc::new(response_header_overwrites),
)
.cell())
}
ContentSourceContent::HttpProxy(proxy_result) => {
break Ok(ResolveSourceRequestResult::HttpProxy(*proxy_result).cell())
Expand Down
4 changes: 2 additions & 2 deletions crates/turbopack-dev-server/src/update/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async fn get_update_stream_item(
let content = get_content();

match &*content.await? {
ResolveSourceRequestResult::Static(static_content) => {
ResolveSourceRequestResult::Static(static_content, _) => {
let resolved_content = static_content.await?.content;
let from = from.get();
let update = resolved_content.update(from);
Expand Down Expand Up @@ -138,7 +138,7 @@ impl UpdateStream {
// We can ignore issues reported in content here since [compute_update_stream]
// will handle them
let version = match &*content.await? {
ResolveSourceRequestResult::Static(static_content) => {
ResolveSourceRequestResult::Static(static_content, _) => {
static_content.await?.content.version()
}
_ => NotFoundVersionVc::new().into(),
Expand Down
4 changes: 2 additions & 2 deletions crates/turbopack-node/src/render/render_static.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use turbopack_core::{
};
use turbopack_dev_server::{
html::DevHtmlAssetVc,
source::{HeaderListVc, RewriteVc},
source::{HeaderListVc, RewriteBuilder, RewriteVc},
};
use turbopack_ecmascript::{chunk::EcmascriptChunkPlaceablesVc, EcmascriptModuleAssetVc};

Expand Down Expand Up @@ -120,7 +120,7 @@ async fn run_static_operation(
.context("receiving from node.js process")?
{
RenderStaticIncomingMessage::Rewrite { path } => {
StaticResultVc::rewrite(RewriteVc::new_path_query(path))
StaticResultVc::rewrite(RewriteBuilder::new(path).build())
}
RenderStaticIncomingMessage::Response {
status_code,
Expand Down

1 comment on commit c1ac2ee

@vercel
Copy link

@vercel vercel bot commented on c1ac2ee Feb 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.