Adds support for Astro.clientAddress (#3973)
* Adds support for Astro.clientAddress * Pass through mode and adapterName in SSG * Pass through the mode provided * Provide an adapter specific error message when possible
This commit is contained in:
parent
d73c04a9e5
commit
5a23483efb
25 changed files with 311 additions and 49 deletions
18
.changeset/olive-dryers-sell.md
Normal file
18
.changeset/olive-dryers-sell.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
'astro': minor
|
||||
'@astrojs/cloudflare': minor
|
||||
'@astrojs/deno': minor
|
||||
'@astrojs/netlify': minor
|
||||
'@astrojs/vercel': minor
|
||||
'@astrojs/node': minor
|
||||
---
|
||||
|
||||
Adds support for Astro.clientAddress
|
||||
|
||||
The new `Astro.clientAddress` property allows you to get the IP address of the requested user.
|
||||
|
||||
```astro
|
||||
<div>Your address { Astro.clientAddress }</div>
|
||||
```
|
||||
|
||||
This property is only available when building for SSR, and only if the adapter you are using supports providing the IP address. If you attempt to access the property in a SSG app it will throw an error.
|
|
@ -92,6 +92,10 @@ export interface AstroGlobal extends AstroGlobalPartial {
|
|||
* [Astro reference](https://docs.astro.build/en/reference/api-reference/#astrocanonicalurl)
|
||||
*/
|
||||
canonicalURL: URL;
|
||||
/** The address (usually IP address) of the user. Used with SSR only.
|
||||
*
|
||||
*/
|
||||
clientAddress: string;
|
||||
/** Parameters passed to a dynamic page generated using [getStaticPaths](https://docs.astro.build/en/reference/api-reference/#getstaticpaths)
|
||||
*
|
||||
* Example usage:
|
||||
|
|
|
@ -10,6 +10,7 @@ import type { RouteInfo, SSRManifest as Manifest } from './types';
|
|||
|
||||
import mime from 'mime';
|
||||
import { call as callEndpoint } from '../endpoint/index.js';
|
||||
import { error } from '../logger/core.js';
|
||||
import { consoleLogDestination } from '../logger/console.js';
|
||||
import { joinPaths, prependForwardSlash } from '../path.js';
|
||||
import { render } from '../render/core.js';
|
||||
|
@ -96,33 +97,43 @@ export class App {
|
|||
}
|
||||
}
|
||||
|
||||
const response = await render({
|
||||
links,
|
||||
logging: this.#logging,
|
||||
markdown: manifest.markdown,
|
||||
mod,
|
||||
origin: url.origin,
|
||||
pathname: url.pathname,
|
||||
scripts,
|
||||
renderers,
|
||||
async resolve(specifier: string) {
|
||||
if (!(specifier in manifest.entryModules)) {
|
||||
throw new Error(`Unable to resolve [${specifier}]`);
|
||||
}
|
||||
const bundlePath = manifest.entryModules[specifier];
|
||||
return bundlePath.startsWith('data:')
|
||||
? bundlePath
|
||||
: prependForwardSlash(joinPaths(manifest.base, bundlePath));
|
||||
},
|
||||
route: routeData,
|
||||
routeCache: this.#routeCache,
|
||||
site: this.#manifest.site,
|
||||
ssr: true,
|
||||
request,
|
||||
streaming: this.#streaming,
|
||||
});
|
||||
|
||||
return response;
|
||||
try {
|
||||
const response = await render({
|
||||
adapterName: manifest.adapterName,
|
||||
links,
|
||||
logging: this.#logging,
|
||||
markdown: manifest.markdown,
|
||||
mod,
|
||||
mode: 'production',
|
||||
origin: url.origin,
|
||||
pathname: url.pathname,
|
||||
scripts,
|
||||
renderers,
|
||||
async resolve(specifier: string) {
|
||||
if (!(specifier in manifest.entryModules)) {
|
||||
throw new Error(`Unable to resolve [${specifier}]`);
|
||||
}
|
||||
const bundlePath = manifest.entryModules[specifier];
|
||||
return bundlePath.startsWith('data:')
|
||||
? bundlePath
|
||||
: prependForwardSlash(joinPaths(manifest.base, bundlePath));
|
||||
},
|
||||
route: routeData,
|
||||
routeCache: this.#routeCache,
|
||||
site: this.#manifest.site,
|
||||
ssr: true,
|
||||
request,
|
||||
streaming: this.#streaming,
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch(err) {
|
||||
error(this.#logging, 'ssr', err);
|
||||
return new Response(null, {
|
||||
status: 500,
|
||||
statusText: 'Internal server error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async #callEndpoint(
|
||||
|
|
|
@ -5,13 +5,19 @@ import { IncomingMessage } from 'http';
|
|||
import { deserializeManifest } from './common.js';
|
||||
import { App } from './index.js';
|
||||
|
||||
const clientAddressSymbol = Symbol.for('astro.clientAddress');
|
||||
|
||||
function createRequestFromNodeRequest(req: IncomingMessage): Request {
|
||||
let url = `http://${req.headers.host}${req.url}`;
|
||||
const entries = Object.entries(req.headers as Record<string, any>);
|
||||
let rawHeaders = req.headers as Record<string, any>;
|
||||
const entries = Object.entries(rawHeaders);
|
||||
let request = new Request(url, {
|
||||
method: req.method || 'GET',
|
||||
headers: new Headers(entries),
|
||||
});
|
||||
if(req.socket.remoteAddress) {
|
||||
Reflect.set(request, clientAddressSymbol, req.socket.remoteAddress);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ export type SerializedRouteInfo = Omit<RouteInfo, 'routeData'> & {
|
|||
};
|
||||
|
||||
export interface SSRManifest {
|
||||
adapterName: string;
|
||||
routes: RouteInfo[];
|
||||
site?: string;
|
||||
base?: string;
|
||||
|
|
|
@ -210,10 +210,12 @@ async function generatePath(
|
|||
const ssr = isBuildingToSSR(opts.astroConfig);
|
||||
const url = new URL(opts.astroConfig.base + removeLeadingForwardSlash(pathname), origin);
|
||||
const options: RenderOptions = {
|
||||
adapterName: undefined,
|
||||
links,
|
||||
logging,
|
||||
markdown: astroConfig.markdown,
|
||||
mod,
|
||||
mode: opts.mode,
|
||||
origin,
|
||||
pathname,
|
||||
scripts,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { AstroTelemetry } from '@astrojs/telemetry';
|
||||
import type { AstroConfig, BuildConfig, ManifestData } from '../../@types/astro';
|
||||
import type { AstroConfig, BuildConfig, ManifestData, RuntimeMode } from '../../@types/astro';
|
||||
import type { LogOptions } from '../logger/core';
|
||||
|
||||
import fs from 'fs';
|
||||
|
@ -24,7 +24,7 @@ import { staticBuild } from './static-build.js';
|
|||
import { getTimeStat } from './util.js';
|
||||
|
||||
export interface BuildOptions {
|
||||
mode?: string;
|
||||
mode?: RuntimeMode;
|
||||
logging: LogOptions;
|
||||
telemetry: AstroTelemetry;
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ export default async function build(config: AstroConfig, options: BuildOptions):
|
|||
class AstroBuilder {
|
||||
private config: AstroConfig;
|
||||
private logging: LogOptions;
|
||||
private mode = 'production';
|
||||
private mode: RuntimeMode = 'production';
|
||||
private origin: string;
|
||||
private routeCache: RouteCache;
|
||||
private manifest: ManifestData;
|
||||
|
@ -129,17 +129,25 @@ class AstroBuilder {
|
|||
colors.dim(`Completed in ${getTimeStat(this.timer.init, performance.now())}.`)
|
||||
);
|
||||
|
||||
await staticBuild({
|
||||
allPages,
|
||||
astroConfig: this.config,
|
||||
logging: this.logging,
|
||||
manifest: this.manifest,
|
||||
origin: this.origin,
|
||||
pageNames,
|
||||
routeCache: this.routeCache,
|
||||
viteConfig,
|
||||
buildConfig,
|
||||
});
|
||||
try {
|
||||
await staticBuild({
|
||||
allPages,
|
||||
astroConfig: this.config,
|
||||
logging: this.logging,
|
||||
manifest: this.manifest,
|
||||
mode: this.mode,
|
||||
origin: this.origin,
|
||||
pageNames,
|
||||
routeCache: this.routeCache,
|
||||
viteConfig,
|
||||
buildConfig,
|
||||
});
|
||||
} catch(err: unknown) {
|
||||
// If the build doesn't complete, still shutdown the Vite server so the process doesn't hang.
|
||||
await viteServer.close();
|
||||
throw err;
|
||||
}
|
||||
|
||||
|
||||
// Write any additionally generated assets to disk.
|
||||
this.timer.assetsStart = performance.now();
|
||||
|
|
|
@ -145,7 +145,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
|
|||
...(viteConfig.plugins || []),
|
||||
// SSR needs to be last
|
||||
isBuildingToSSR(opts.astroConfig) &&
|
||||
vitePluginSSR(opts, internals, opts.astroConfig._ctx.adapter!),
|
||||
vitePluginSSR(internals, opts.astroConfig._ctx.adapter!),
|
||||
vitePluginAnalyzer(opts.astroConfig, internals),
|
||||
],
|
||||
publicDir: ssr ? false : viteConfig.publicDir,
|
||||
|
|
|
@ -4,6 +4,7 @@ import type {
|
|||
ComponentInstance,
|
||||
ManifestData,
|
||||
RouteData,
|
||||
RuntimeMode,
|
||||
SSRLoadedRenderer,
|
||||
} from '../../@types/astro';
|
||||
import type { ViteConfigWithSSR } from '../create-vite';
|
||||
|
@ -30,6 +31,7 @@ export interface StaticBuildOptions {
|
|||
buildConfig: BuildConfig;
|
||||
logging: LogOptions;
|
||||
manifest: ManifestData;
|
||||
mode: RuntimeMode;
|
||||
origin: string;
|
||||
pageNames: string[];
|
||||
routeCache: RouteCache;
|
||||
|
|
|
@ -20,7 +20,6 @@ const manifestReplace = '@@ASTRO_MANIFEST_REPLACE@@';
|
|||
const replaceExp = new RegExp(`['"](${manifestReplace})['"]`, 'g');
|
||||
|
||||
export function vitePluginSSR(
|
||||
buildOpts: StaticBuildOptions,
|
||||
internals: BuildInternals,
|
||||
adapter: AstroAdapter
|
||||
): VitePlugin {
|
||||
|
@ -153,6 +152,7 @@ function buildManifest(
|
|||
'data:text/javascript;charset=utf-8,//[no before-hydration script]';
|
||||
|
||||
const ssrManifest: SerializedSSRManifest = {
|
||||
adapterName: opts.astroConfig._ctx.adapter!.name,
|
||||
routes,
|
||||
site: astroConfig.site,
|
||||
base: astroConfig.base,
|
||||
|
|
|
@ -4,6 +4,7 @@ import type {
|
|||
Params,
|
||||
Props,
|
||||
RouteData,
|
||||
RuntimeMode,
|
||||
SSRElement,
|
||||
SSRLoadedRenderer,
|
||||
} from '../../@types/astro';
|
||||
|
@ -66,11 +67,13 @@ export async function getParamsAndProps(
|
|||
}
|
||||
|
||||
export interface RenderOptions {
|
||||
adapterName: string | undefined;
|
||||
logging: LogOptions;
|
||||
links: Set<SSRElement>;
|
||||
styles?: Set<SSRElement>;
|
||||
markdown: MarkdownRenderingOptions;
|
||||
mod: ComponentInstance;
|
||||
mode: RuntimeMode;
|
||||
origin: string;
|
||||
pathname: string;
|
||||
scripts: Set<SSRElement>;
|
||||
|
@ -86,12 +89,14 @@ export interface RenderOptions {
|
|||
|
||||
export async function render(opts: RenderOptions): Promise<Response> {
|
||||
const {
|
||||
adapterName,
|
||||
links,
|
||||
styles,
|
||||
logging,
|
||||
origin,
|
||||
markdown,
|
||||
mod,
|
||||
mode,
|
||||
pathname,
|
||||
scripts,
|
||||
renderers,
|
||||
|
@ -126,10 +131,12 @@ export async function render(opts: RenderOptions): Promise<Response> {
|
|||
throw new Error(`Expected an exported Astro component but received typeof ${typeof Component}`);
|
||||
|
||||
const result = createResult({
|
||||
adapterName,
|
||||
links,
|
||||
styles,
|
||||
logging,
|
||||
markdown,
|
||||
mode,
|
||||
origin,
|
||||
params,
|
||||
props: pageProps,
|
||||
|
|
|
@ -161,11 +161,13 @@ export async function render(
|
|||
});
|
||||
|
||||
let response = await coreRender({
|
||||
adapterName: astroConfig.adapter?.name,
|
||||
links,
|
||||
styles,
|
||||
logging,
|
||||
markdown: astroConfig.markdown,
|
||||
mod,
|
||||
mode,
|
||||
origin,
|
||||
pathname,
|
||||
scripts,
|
||||
|
|
|
@ -6,6 +6,7 @@ import type {
|
|||
Page,
|
||||
Params,
|
||||
Props,
|
||||
RuntimeMode,
|
||||
SSRElement,
|
||||
SSRLoadedRenderer,
|
||||
SSRResult,
|
||||
|
@ -15,6 +16,8 @@ import { LogOptions, warn } from '../logger/core.js';
|
|||
import { isScriptRequest } from './script.js';
|
||||
import { createCanonicalURL, isCSSRequest } from './util.js';
|
||||
|
||||
const clientAddressSymbol = Symbol.for('astro.clientAddress');
|
||||
|
||||
function onlyAvailableInSSR(name: string) {
|
||||
return function _onlyAvailableInSSR() {
|
||||
// TODO add more guidance when we have docs and adapters.
|
||||
|
@ -23,11 +26,13 @@ function onlyAvailableInSSR(name: string) {
|
|||
}
|
||||
|
||||
export interface CreateResultArgs {
|
||||
adapterName: string | undefined;
|
||||
ssr: boolean;
|
||||
streaming: boolean;
|
||||
logging: LogOptions;
|
||||
origin: string;
|
||||
markdown: MarkdownRenderingOptions;
|
||||
mode: RuntimeMode;
|
||||
params: Params;
|
||||
pathname: string;
|
||||
props: Props;
|
||||
|
@ -151,6 +156,17 @@ export function createResult(args: CreateResultArgs): SSRResult {
|
|||
const Astro = {
|
||||
__proto__: astroGlobal,
|
||||
canonicalURL,
|
||||
get clientAddress() {
|
||||
if(!(clientAddressSymbol in request)) {
|
||||
if(args.adapterName) {
|
||||
throw new Error(`Astro.clientAddress is not available in the ${args.adapterName} adapter. File an issue with the adapter to add support.`);
|
||||
} else {
|
||||
throw new Error(`Astro.clientAddress is not available in your environment. Ensure that you are using an SSR adapter that supports this feature.`)
|
||||
}
|
||||
}
|
||||
|
||||
return Reflect.get(request, clientAddressSymbol);
|
||||
},
|
||||
params,
|
||||
props,
|
||||
request,
|
||||
|
|
|
@ -7,6 +7,7 @@ type RequestBody = ArrayBuffer | Blob | ReadableStream | URLSearchParams | FormD
|
|||
|
||||
export interface CreateRequestOptions {
|
||||
url: URL | string;
|
||||
clientAddress?: string | undefined;
|
||||
headers: HeaderType;
|
||||
method?: string;
|
||||
body?: RequestBody | undefined;
|
||||
|
@ -14,9 +15,12 @@ export interface CreateRequestOptions {
|
|||
ssr: boolean;
|
||||
}
|
||||
|
||||
const clientAddressSymbol = Symbol.for('astro.clientAddress');
|
||||
|
||||
export function createRequest({
|
||||
url,
|
||||
headers,
|
||||
clientAddress,
|
||||
method = 'GET',
|
||||
body = undefined,
|
||||
logging,
|
||||
|
@ -67,6 +71,8 @@ export function createRequest({
|
|||
return _headers;
|
||||
},
|
||||
});
|
||||
} else if(clientAddress) {
|
||||
Reflect.set(request, clientAddressSymbol, clientAddress);
|
||||
}
|
||||
|
||||
return request;
|
||||
|
|
|
@ -229,6 +229,7 @@ async function handleRequest(
|
|||
body,
|
||||
logging,
|
||||
ssr: buildingToSSR,
|
||||
clientAddress: buildingToSSR ? req.socket.remoteAddress : undefined,
|
||||
});
|
||||
|
||||
try {
|
||||
|
|
130
packages/astro/test/client-address.test.js
Normal file
130
packages/astro/test/client-address.test.js
Normal file
|
@ -0,0 +1,130 @@
|
|||
import { expect } from 'chai';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
import testAdapter from './test-adapter.js';
|
||||
import { nodeLogDestination } from '../dist/core/logger/node.js';
|
||||
import * as cheerio from 'cheerio';
|
||||
|
||||
describe('Astro.clientAddress', () => {
|
||||
describe('SSR', () => {
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/client-address/',
|
||||
experimental: {
|
||||
ssr: true,
|
||||
},
|
||||
adapter: testAdapter(),
|
||||
});
|
||||
});
|
||||
|
||||
describe('Production', () => {
|
||||
before(async () => {
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
it('Can get the address', async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
const request = new Request('http://example.com/');
|
||||
const response = await app.render(request);
|
||||
const html = await response.text();
|
||||
const $ = cheerio.load(html);
|
||||
expect($('#address').text()).to.equal('0.0.0.0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Development', () => {
|
||||
/** @type {import('./test-utils').DevServer} */
|
||||
let devServer;
|
||||
|
||||
before(async () => {
|
||||
devServer = await fixture.startDevServer();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await devServer.stop();
|
||||
});
|
||||
|
||||
it('Gets the address', async () => {
|
||||
let res = await fixture.fetch('/');
|
||||
expect(res.status).to.equal(200);
|
||||
let html = await res.text();
|
||||
let $ = cheerio.load(html);
|
||||
let address = $('#address');
|
||||
|
||||
// Just checking that something is here. Not specifying address as it
|
||||
// might differ per machine.
|
||||
expect(address.length).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SSR adapter not implemented', () => {
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/client-address/',
|
||||
experimental: {
|
||||
ssr: true,
|
||||
},
|
||||
adapter: testAdapter({ provideAddress: false }),
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
it('Gets an error message', async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
const request = new Request('http://example.com/');
|
||||
const response = await app.render(request);
|
||||
expect(response.status).to.equal(500);
|
||||
});
|
||||
})
|
||||
|
||||
describe('SSG', () => {
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/client-address/',
|
||||
});
|
||||
});
|
||||
|
||||
describe('Build', () => {
|
||||
it('throws during generation', async () => {
|
||||
try {
|
||||
await fixture.build();
|
||||
expect(false).to.equal(true, 'Build should not have completed');
|
||||
} catch(err) {
|
||||
expect(err.message).to.match(/Astro\.clientAddress/, 'Error message mentions Astro.clientAddress');
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
describe('Development', () => {
|
||||
/** @type {import('./test-utils').DevServer} */
|
||||
let devServer;
|
||||
|
||||
before(async () => {
|
||||
// We expect an error, so silence the output
|
||||
const logging = {
|
||||
dest: nodeLogDestination,
|
||||
level: 'silent',
|
||||
};
|
||||
devServer = await fixture.startDevServer({ logging });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await devServer.stop();
|
||||
});
|
||||
|
||||
it('is not accessible', async () => {
|
||||
let res = await fixture.fetch('/');
|
||||
expect(res.status).to.equal(500);
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
12
packages/astro/test/fixtures/client-address/src/pages/index.astro
vendored
Normal file
12
packages/astro/test/fixtures/client-address/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
const address = Astro.clientAddress;
|
||||
---
|
||||
<html>
|
||||
<head>
|
||||
<title>Astro.clientAddress</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Astro.clientAddress</h1>
|
||||
<div id="address">{ address }</div>
|
||||
</body>
|
||||
</html>
|
|
@ -4,7 +4,7 @@ import { viteID } from '../dist/core/util.js';
|
|||
*
|
||||
* @returns {import('../src/@types/astro').AstroIntegration}
|
||||
*/
|
||||
export default function () {
|
||||
export default function ({ provideAddress } = { provideAddress: true }) {
|
||||
return {
|
||||
name: 'my-ssr-adapter',
|
||||
hooks: {
|
||||
|
@ -23,7 +23,23 @@ export default function () {
|
|||
},
|
||||
load(id) {
|
||||
if (id === '@my-ssr') {
|
||||
return `import { App } from 'astro/app';export function createExports(manifest) { return { manifest, createApp: (streaming) => new App(manifest, streaming) }; }`;
|
||||
return `
|
||||
import { App } from 'astro/app';
|
||||
|
||||
class MyApp extends App {
|
||||
render(request) {
|
||||
${provideAddress ? `request[Symbol.for('astro.clientAddress')] = '0.0.0.0';` : ''}
|
||||
return super.render(request);
|
||||
}
|
||||
}
|
||||
|
||||
export function createExports(manifest) {
|
||||
return {
|
||||
manifest,
|
||||
createApp: (streaming) => new MyApp(manifest, streaming)
|
||||
};
|
||||
}
|
||||
`;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -127,6 +127,7 @@ export async function loadFixture(inlineConfig) {
|
|||
// Also do it on process exit, just in case.
|
||||
process.on('exit', resetAllFiles);
|
||||
|
||||
let fixtureId = new Date().valueOf();
|
||||
let devServer;
|
||||
|
||||
return {
|
||||
|
@ -150,7 +151,7 @@ export async function loadFixture(inlineConfig) {
|
|||
await fs.promises.rm(config.outDir, { maxRetries: 10, recursive: true, force: true });
|
||||
},
|
||||
loadTestAdapterApp: async (streaming) => {
|
||||
const url = new URL('./server/entry.mjs', config.outDir);
|
||||
const url = new URL(`./server/entry.mjs?id=${fixtureId}`, config.outDir);
|
||||
const { createApp, manifest } = await import(url);
|
||||
const app = createApp(streaming);
|
||||
app.manifest = manifest;
|
||||
|
|
|
@ -20,6 +20,7 @@ export function createExports(manifest: SSRManifest) {
|
|||
}
|
||||
|
||||
if (app.match(request)) {
|
||||
Reflect.set(request, Symbol.for('astro.clientAddress'), request.headers.get('cf-connecting-ip'));
|
||||
return app.render(request);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,8 +22,10 @@ export function start(manifest: SSRManifest, options: Options) {
|
|||
|
||||
const clientRoot = new URL('../client/', import.meta.url);
|
||||
const app = new App(manifest);
|
||||
const handler = async (request: Request) => {
|
||||
const handler = async (request: Request, connInfo: any) => {
|
||||
if (app.match(request)) {
|
||||
let ip = connInfo?.remoteAddr?.hostname;
|
||||
Reflect.set(request, Symbol.for('astro.clientAddress'), ip);
|
||||
return await app.render(request);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import type { SSRManifest } from 'astro';
|
||||
import { App } from 'astro/app';
|
||||
|
||||
const clientAddressSymbol = Symbol.for('astro.clientAddress');
|
||||
|
||||
export function createExports(manifest: SSRManifest) {
|
||||
const app = new App(manifest);
|
||||
|
||||
|
@ -13,6 +15,8 @@ export function createExports(manifest: SSRManifest) {
|
|||
return;
|
||||
}
|
||||
if (app.match(request)) {
|
||||
const ip = request.headers.get('x-nf-client-connection-ip');
|
||||
Reflect.set(request, clientAddressSymbol, ip);
|
||||
return app.render(request);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ function parseContentType(header?: string) {
|
|||
return header?.split(';')[0] ?? '';
|
||||
}
|
||||
|
||||
const clientAddressSymbol = Symbol.for('astro.clientAddress');
|
||||
|
||||
export const createExports = (manifest: SSRManifest, args: Args) => {
|
||||
const app = new App(manifest);
|
||||
|
||||
|
@ -71,6 +73,9 @@ export const createExports = (manifest: SSRManifest, args: Args) => {
|
|||
};
|
||||
}
|
||||
|
||||
const ip = headers['x-nf-client-connection-ip'];
|
||||
Reflect.set(request, clientAddressSymbol, ip);
|
||||
|
||||
const response: Response = await app.render(request);
|
||||
const responseHeaders = Object.fromEntries(response.headers.entries());
|
||||
|
||||
|
|
|
@ -7,11 +7,14 @@ import './shim.js';
|
|||
import type { SSRManifest } from 'astro';
|
||||
import { App } from 'astro/app';
|
||||
|
||||
const clientAddressSymbol = Symbol.for('astro.clientAddress');
|
||||
|
||||
export function createExports(manifest: SSRManifest) {
|
||||
const app = new App(manifest);
|
||||
|
||||
const handler = async (request: Request): Promise<Response> => {
|
||||
if (app.match(request)) {
|
||||
Reflect.set(request, clientAddressSymbol, request.headers.get('x-forwarded-for'));
|
||||
return await app.render(request);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import { Readable } from 'node:stream';
|
||||
|
||||
const clientAddressSymbol = Symbol.for('astro.clientAddress');
|
||||
|
||||
/*
|
||||
Credits to the SvelteKit team
|
||||
https://github.com/sveltejs/kit/blob/69913e9fda054fa6a62a80e2bb4ee7dca1005796/packages/kit/src/node.js
|
||||
|
@ -66,11 +68,13 @@ export async function getRequest(base: string, req: IncomingMessage): Promise<Re
|
|||
delete headers[':authority'];
|
||||
delete headers[':scheme'];
|
||||
}
|
||||
return new Request(base + req.url, {
|
||||
const request = new Request(base + req.url, {
|
||||
method: req.method,
|
||||
headers,
|
||||
body: await get_raw_body(req), // TODO stream rather than buffer
|
||||
});
|
||||
Reflect.set(request, clientAddressSymbol, headers['x-forwarded-for']);
|
||||
return request;
|
||||
}
|
||||
|
||||
export async function setResponse(res: ServerResponse, response: Response): Promise<void> {
|
||||
|
|
Loading…
Reference in a new issue