Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add x-forwarded-* headers to turbopack renders #50012

Merged
merged 5 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import type { RenderOpts } from 'next/dist/server/app-render/types'

import { renderToHTMLOrFlight } from 'next/dist/server/app-render/app-render'
import { RSC_VARY_HEADER } from 'next/dist/client/components/app-router-headers'
import { headersFromEntries } from '../internal/headers'
import { headersFromEntries, initProxiedHeaders } from '../internal/headers'
import { parse, ParsedUrlQuery } from 'node:querystring'
import { PassThrough } from 'node:stream'
;('TURBOPACK { transition: next-layout-entry; chunking-type: isolatedParallel }')
Expand Down Expand Up @@ -247,7 +247,10 @@ async function runOperation(renderData: RenderData) {
const req: IncomingMessage = {
url: renderData.originalUrl,
method: renderData.method,
headers: headersFromEntries(renderData.rawHeaders),
headers: initProxiedHeaders(
headersFromEntries(renderData.rawHeaders),
renderData.data?.serverInfo
),
} as any

const res = createServerResponse(req, renderData.path)
Expand Down
16 changes: 8 additions & 8 deletions packages/next-swc/crates/next-core/js/src/entry/router.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import type { Ipc, StructuredError } from '@vercel/turbopack-node/ipc/index'
import type { IncomingMessage, ServerResponse } from 'node:http'
import { Buffer } from 'node:buffer'
import { createServer, makeRequest } from '../internal/server'
import { createServer, makeRequest, type ServerInfo } from '../internal/server'
import { toPairs } from '../internal/headers'
import {
makeResolver,
RouteResult,
ServerAddress,
type RouteResult,
} from 'next/dist/server/lib/route-resolver'
import loadConfig from 'next/dist/server/config'
import { PHASE_DEVELOPMENT_SERVER } from 'next/dist/shared/lib/constants'
Expand Down Expand Up @@ -58,7 +57,7 @@ let resolveRouteMemo: Promise<

async function getResolveRoute(
dir: string,
serverAddr: Partial<ServerAddress>
serverInfo: ServerInfo
): ReturnType<
typeof import('next/dist/server/lib/route-resolver').makeResolver
> {
Expand All @@ -74,17 +73,17 @@ async function getResolveRoute(
matcher: middlewareConfig.matcher,
}

return await makeResolver(dir, nextConfig, middlewareCfg, serverAddr)
return await makeResolver(dir, nextConfig, middlewareCfg, serverInfo)
}

export default async function route(
ipc: Ipc<RouterRequest, IpcOutgoingMessage>,
routerRequest: RouterRequest,
dir: string,
serverAddr: Partial<ServerAddress>
serverInfo: ServerInfo
) {
const [resolveRoute, server] = await Promise.all([
(resolveRouteMemo ??= getResolveRoute(dir, serverAddr)),
(resolveRouteMemo ??= getResolveRoute(dir, serverInfo)),
createServer(),
])

Expand All @@ -99,7 +98,8 @@ export default async function route(
routerRequest.method,
routerRequest.pathname,
routerRequest.rawQuery,
routerRequest.rawHeaders
routerRequest.rawHeaders,
serverInfo
)

const body = Buffer.concat(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ export default function startHandler(handler: Handler): void {
renderData.method,
renderData.path,
renderData.rawQuery,
renderData.rawHeaders
renderData.rawHeaders,
renderData.data?.serverInfo
)

return {
Expand Down
24 changes: 21 additions & 3 deletions packages/next-swc/crates/next-core/js/src/internal/headers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ServerInfo } from './server'

export type Headers = Record<string, string | string[]>
/**
* Converts an array of raw header entries to a map of header names to values.
*/
export function headersFromEntries(
entries: Array<[string, string]>
): Record<string, string | string[]> {
export function headersFromEntries(entries: Array<[string, string]>): Headers {
const headers: Record<string, string | string[]> = Object.create(null)
for (const [key, value] of entries) {
if (key in headers) {
Expand Down Expand Up @@ -41,3 +42,20 @@ export function toPairs<T>(arr: T[]): Array<[T, T]> {

return pairs
}

/**
* These headers are provided by default to match the http-proxy behavior
* https://github.com/http-party/node-http-proxy/blob/9b96cd72/lib/http-proxy/passes/web-incoming.js#L58-L86
*/
export function initProxiedHeaders(
headers: Headers,
proxiedFor: ServerInfo | null | undefined
): Headers {
const hostname = proxiedFor?.hostname || 'localhost'
const port = String(proxiedFor?.port || 3000)
headers['x-forwarded-for'] = proxiedFor?.ip || '::1'
headers['x-forwarded-host'] = `${hostname}:${port}`
headers['x-forwarded-port'] = port
headers['x-forwarded-proto'] = proxiedFor?.protocol || 'http'
return headers
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { buildStaticPaths } from 'next/dist/build/utils'
import type { BuildManifest } from 'next/dist/server/get-page-files'
import type { ReactLoadableManifest } from 'next/dist/server/load-components'

import { headersFromEntries } from './headers'
import { headersFromEntries, initProxiedHeaders } from './headers'
import { createServerResponse } from './http'
import type { Ipc } from '@vercel/turbopack-node/ipc/index'
import type { RenderData } from 'types/turbopack'
Expand Down Expand Up @@ -218,7 +218,10 @@ export default function startHandler({
const req: IncomingMessage = {
url: renderData.url,
method: 'GET',
headers: headersFromEntries(renderData.rawHeaders),
headers: initProxiedHeaders(
headersFromEntries(renderData.rawHeaders),
renderData.data?.serverInfo
),
} as any
const res: ServerResponse = createServerResponse(req, renderData.path)

Expand Down
17 changes: 14 additions & 3 deletions packages/next-swc/crates/next-core/js/src/internal/server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import type { ClientRequest, IncomingMessage, Server } from 'node:http'
import type { AddressInfo } from 'node:net'
import http, { ServerResponse } from 'node:http'
import { headersFromEntries } from './headers'
import { headersFromEntries, initProxiedHeaders } from './headers'

export type ServerInfo = Partial<{
hostname: string
port: number
ip: string
protocol: string
}>

/**
* Creates a server that listens a random port.
Expand All @@ -24,7 +31,8 @@ export function makeRequest(
method: string,
path: string,
rawQuery?: string,
rawHeaders?: [string, string][]
rawHeaders?: [string, string][],
proxiedFor?: ServerInfo
): Promise<{
clientRequest: ClientRequest
clientResponsePromise: Promise<IncomingMessage>
Expand Down Expand Up @@ -101,13 +109,16 @@ export function makeRequest(

const address = server.address() as AddressInfo

const headers = headersFromEntries(rawHeaders ?? [])
initProxiedHeaders(headers, proxiedFor)

clientRequest = http.request({
host: 'localhost',
port: address.port,
method,
path:
rawQuery != null && rawQuery.length > 0 ? `${path}?${rawQuery}` : path,
headers: rawHeaders != null ? headersFromEntries(rawHeaders) : undefined,
headers,
})

// Otherwise Node.js waits for the first chunk of data to be written before sending the request.
Expand Down
2 changes: 2 additions & 0 deletions packages/next-swc/crates/next-core/js/types/turbopack.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ServerInfo } from '@vercel/turbopack-next/internal/server'
import type { RenderOptsPartial } from 'next/dist/server/render'

export type RenderData = {
Expand All @@ -10,5 +11,6 @@ export type RenderData = {
rawHeaders: Array<[string, string]>
data?: {
nextConfigOutput?: RenderOptsPartial['nextConfigOutput']
serverInfo?: ServerInfo
}
}
2 changes: 1 addition & 1 deletion packages/next-swc/crates/next-core/src/app_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ pub async fn create_app_source(
client_compile_time_info,
next_config,
);
let render_data = render_data(next_config);
let render_data = render_data(next_config, server_addr);

let entrypoints = entrypoints.await?;
let mut sources: Vec<_> = entrypoints
Expand Down
2 changes: 1 addition & 1 deletion packages/next-swc/crates/next-core/src/page_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ pub async fn create_page_source(
next_config,
);

let render_data = render_data(next_config);
let render_data = render_data(next_config, server_addr);
let page_extensions = next_config.page_extensions();

let mut sources = vec![];
Expand Down
7 changes: 2 additions & 5 deletions packages/next-swc/crates/next-core/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use turbopack_binding::{
changed::any_content_changed,
chunk::ChunkingContext,
context::{AssetContext, AssetContextVc},
environment::{EnvironmentIntention::Middleware, ServerAddrVc},
environment::{EnvironmentIntention::Middleware, ServerAddrVc, ServerInfo},
ident::AssetIdentVc,
issue::IssueVc,
reference_type::{EcmaScriptModulesReferenceSubType, ReferenceType},
Expand Down Expand Up @@ -388,10 +388,7 @@ async fn route_internal(
vec![
JsonValueVc::cell(request),
JsonValueVc::cell(dir.to_string_lossy().into()),
JsonValueVc::cell(json!({
"hostname": server_addr.hostname(),
"port": server_addr.port(),
})),
JsonValueVc::cell(serde_json::to_value(ServerInfo::try_from(&*server_addr)?)?),
],
CompletionsVc::all(vec![next_config_changed, routes_changed]),
/* debug */ false,
Expand Down
9 changes: 8 additions & 1 deletion packages/next-swc/crates/next-core/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use turbopack_binding::{
turbopack::{
core::{
asset::{Asset, AssetVc},
environment::{ServerAddrVc, ServerInfo},
ident::AssetIdentVc,
issue::{Issue, IssueSeverity, IssueSeverityVc, IssueVc, OptionIssueSourceVc},
reference_type::{EcmaScriptModulesReferenceSubType, ReferenceType},
Expand Down Expand Up @@ -356,17 +357,23 @@ pub async fn load_next_json<T: DeserializeOwned>(
}

#[turbo_tasks::function]
pub async fn render_data(next_config: NextConfigVc) -> Result<JsonValueVc> {
pub async fn render_data(
next_config: NextConfigVc,
server_addr: ServerAddrVc,
) -> Result<JsonValueVc> {
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Data {
next_config_output: Option<OutputType>,
server_info: Option<ServerInfo>,
}

let config = next_config.await?;
let server_info = ServerInfo::try_from(&*server_addr.await?);

let value = serde_json::to_value(Data {
next_config_output: config.output.clone(),
server_info: server_info.ok(),
})?;
Ok(JsonValue(value).cell())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
const headers = request.headers
return NextResponse.json({
host: headers.get('x-forwarded-host'),
ip: headers.get('x-forwarded-ip'),
port: headers.get('x-forwarded-port'),
proto: headers.get('x-forwarded-proto'),
})
}

export const config = {
matcher: '/headers',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** @type {import('next').NextConfig} */
module.exports = {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useTestHarness } from '@turbo/pack-test-harness'

export default function Foo() {
useTestHarness(runTests)

return 'index'
}

function runTests() {
it('should stream middleware response from node', async () => {
const res = await fetch('/headers')
const json = await res.json()

const { host, port, proto } = json
expect(host).toBe(`localhost:${port}`)
expect(port).toMatch(/\d+/)
expect(proto).toBe('http')
})
}
4 changes: 2 additions & 2 deletions packages/next/src/server/lib/route-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ export type MiddlewareConfig = {
}

export type ServerAddress = {
hostname: string
port: number
hostname?: string | null
port?: number | null
}

export type RouteResult =
Expand Down