Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions packages/php-wasm/node/src/test/php-request-handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ describe.each(SupportedPHPVersions)(
php = new NodePHP(runtimeId);
handler = new PHPRequestHandler(php, {
documentRoot: '/',
isStaticFilePath: (path) => !path.endsWith('.php'),
});
});

Expand Down Expand Up @@ -57,6 +56,34 @@ describe.each(SupportedPHPVersions)(
});
});

it('should yield x-file-type=static when a static file is not found', async () => {
const response = await handler.request({
url: '/index.html',
});
expect(response).toEqual({
httpStatusCode: 404,
headers: {
'x-file-type': ['static'],
},
bytes: expect.any(Uint8Array),
errors: '',
exitCode: 0,
});
});

it('should not yield x-file-type=static when a PHP file is not found', async () => {
const response = await handler.request({
url: '/index.php',
});
expect(response).toEqual({
httpStatusCode: 404,
headers: {},
bytes: expect.any(Uint8Array),
errors: '',
exitCode: 0,
});
});

it('should only handle a single PHP request at a time', async () => {
php.writeFile(
'/index.php',
Expand Down Expand Up @@ -171,8 +198,6 @@ describe.each(SupportedPHPVersions)(
php.mkdirTree('/var/www');
handler = new PHPRequestHandler(php, {
documentRoot: '/var/www',
// Treat all files as dynamic
isStaticFilePath: () => false,
});
});

Expand Down
58 changes: 41 additions & 17 deletions packages/php-wasm/universal/src/lib/php-request-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@ export interface PHPRequestHandlerConfiguration {
* Request Handler URL. Used to populate $_SERVER details like HTTP_HOST.
*/
absoluteUrl?: string;
/**
* Callback used by the PHPRequestHandler to decide whether
* the requested path refers to a PHP file or a static file.
*/
isStaticFilePath?: (path: string) => boolean;
}

/** @inheritDoc */
Expand All @@ -46,7 +41,6 @@ export class PHPRequestHandler implements RequestHandler {
* The PHP instance
*/
php: BasePHP;
#isStaticFilePath: (path: string) => boolean;

/**
* @param php - The PHP instance.
Expand All @@ -57,11 +51,9 @@ export class PHPRequestHandler implements RequestHandler {
const {
documentRoot = '/www/',
absoluteUrl = typeof location === 'object' ? location?.href : '',
isStaticFilePath = () => false,
} = config;
this.php = php;
this.#DOCROOT = documentRoot;
this.#isStaticFilePath = isStaticFilePath;

const url = new URL(absoluteUrl);
this.#HOSTNAME = url.hostname;
Expand Down Expand Up @@ -122,29 +114,32 @@ export class PHPRequestHandler implements RequestHandler {
isAbsolute ? undefined : DEFAULT_BASE_URL
);

const normalizedRelativeUrl = removePathPrefix(
const normalizedRequestedPath = removePathPrefix(
requestedUrl.pathname,
this.#PATHNAME
);
if (this.#isStaticFilePath(normalizedRelativeUrl)) {
return this.#serveStaticFile(normalizedRelativeUrl);
const fsPath = `${this.#DOCROOT}${normalizedRequestedPath}`;
if (seemsLikeAPHPRequestHandlerPath(fsPath)) {
return await this.#dispatchToPHP(request, requestedUrl);
}
return await this.#dispatchToPHP(request, requestedUrl);
return this.#serveStaticFile(fsPath);
}

/**
* Serves a static file from the PHP filesystem.
*
* @param path - The requested static file path.
* @param fsPath - Absolute path of the static file to serve.
* @returns The response.
*/
#serveStaticFile(path: string): PHPResponse {
const fsPath = `${this.#DOCROOT}${path}`;

#serveStaticFile(fsPath: string): PHPResponse {
if (!this.php.fileExists(fsPath)) {
return new PHPResponse(
404,
{},
// Let the service worker know that no static file was found
// and that it's okay to issue a real fetch() to the server.
{
'x-file-type': ['static'],
},
new TextEncoder().encode('404 File not found')
);
}
Expand Down Expand Up @@ -394,3 +389,32 @@ function inferMimeType(path: string): string {
return 'application-octet-stream';
}
}

/**
* Guesses whether the given path looks like a PHP file.
*
* @example
* ```js
* seemsLikeAPHPRequestHandlerPath('/index.php') // true
* seemsLikeAPHPRequestHandlerPath('/index.php') // true
* seemsLikeAPHPRequestHandlerPath('/index.php/foo/bar') // true
* seemsLikeAPHPRequestHandlerPath('/index.html') // false
* seemsLikeAPHPRequestHandlerPath('/index.html/foo/bar') // false
* seemsLikeAPHPRequestHandlerPath('/') // true
* ```
*
* @param path The path to check.
* @returns Whether the path seems like a PHP server path.
*/
export function seemsLikeAPHPRequestHandlerPath(path: string): boolean {
return seemsLikeAPHPFile(path) || seemsLikeADirectoryRoot(path);
}

function seemsLikeAPHPFile(path: string) {
return path.endsWith('.php') || path.includes('.php/');
}

function seemsLikeADirectoryRoot(path: string) {
const lastSegment = path.split('/').pop();
return !lastSegment!.includes('.');
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,17 @@ export function initializeServiceWorker(config: ServiceWorkerConfiguration) {
async function defaultRequestHandler(event: FetchEvent) {
event.preventDefault();
const url = new URL(event.request.url);
const unscopedUrl = removeURLScope(url);
if (!seemsLikeAPHPRequestHandlerPath(unscopedUrl.pathname)) {
return fetch(
await cloneRequest(event.request, {
url,
})
);
const workerResponse = await convertFetchEventToPHPRequest(event);
if (
workerResponse.status === 404 &&
workerResponse.headers.get('x-file-type') === 'static'
) {
const request = await cloneRequest(event.request, {
url,
});
return fetch(request);
}
return convertFetchEventToPHPRequest(event);
return workerResponse;
}

export async function convertFetchEventToPHPRequest(event: FetchEvent) {
Expand Down Expand Up @@ -192,35 +194,6 @@ interface ServiceWorkerConfiguration {
handleRequest?: (event: FetchEvent) => Promise<Response> | undefined;
}

/**
* Guesses whether the given path looks like a PHP file.
*
* @example
* ```js
* seemsLikeAPHPRequestHandlerPath('/index.php') // true
* seemsLikeAPHPRequestHandlerPath('/index.php') // true
* seemsLikeAPHPRequestHandlerPath('/index.php/foo/bar') // true
* seemsLikeAPHPRequestHandlerPath('/index.html') // false
* seemsLikeAPHPRequestHandlerPath('/index.html/foo/bar') // false
* seemsLikeAPHPRequestHandlerPath('/') // true
* ```
*
* @param path The path to check.
* @returns Whether the path seems like a PHP server path.
*/
export function seemsLikeAPHPRequestHandlerPath(path: string): boolean {
return seemsLikeAPHPFile(path) || seemsLikeADirectoryRoot(path);
}

function seemsLikeAPHPFile(path: string) {
return path.endsWith('.php') || path.includes('.php/');
}

function seemsLikeADirectoryRoot(path: string) {
const lastSegment = path.split('/').pop();
return !lastSegment!.includes('.');
}

async function rewritePost(request: Request) {
const contentType = request.headers.get('content-type')!;
if (request.method !== 'POST') {
Expand Down
1 change: 0 additions & 1 deletion packages/playground/blueprints/src/lib/compile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ describe('Blueprints', () => {
php = await NodePHP.load(phpVersion, {
requestHandler: {
documentRoot: '/',
isStaticFilePath: (path) => !path.endsWith('.php'),
},
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ describe('Blueprint step installPlugin', () => {
php = await NodePHP.load(phpVersion, {
requestHandler: {
documentRoot: '/wordpress',
isStaticFilePath: (path) => !path.endsWith('.php'),
},
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ describe('Blueprint step installTheme', () => {
php = await NodePHP.load(phpVersion, {
requestHandler: {
documentRoot: '/wordpress',
isStaticFilePath: (path) => !path.endsWith('.php'),
},
});
});
Expand Down
28 changes: 12 additions & 16 deletions packages/playground/remote/service-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ import {
awaitReply,
convertFetchEventToPHPRequest,
initializeServiceWorker,
seemsLikeAPHPRequestHandlerPath,
cloneRequest,
broadcastMessageExpectReply,
} from '@php-wasm/web-service-worker';
import { isUploadedFilePath } from './src/lib/is-uploaded-file-path';

if (!(self as any).document) {
// Workaround: vite translates import.meta.url
Expand Down Expand Up @@ -40,23 +38,21 @@ initializeServiceWorker({
}
event.preventDefault();
async function asyncHandler() {
const { staticAssetsDirectory, defaultTheme } =
await getScopedWpDetails(scope!);
const { staticAssetsDirectory } = await getScopedWpDetails(scope!);
const workerResponse = await convertFetchEventToPHPRequest(event);
if (
(seemsLikeAPHPRequestHandlerPath(unscopedUrl.pathname) ||
isUploadedFilePath(unscopedUrl.pathname)) &&
!unscopedUrl.pathname.startsWith(
`/wp-content/themes/${defaultTheme}`
)
workerResponse.status === 404 &&
workerResponse.headers.get('x-file-type') === 'static'
) {
const response = await convertFetchEventToPHPRequest(event);
return response;
// If we get a 404 for a static file, try to fetch it from
// the from the static assets directory at the remote server.
const request = await rewriteRequest(
event.request,
staticAssetsDirectory
);
return fetch(request);
}
const request = await rewriteRequest(
event.request,
staticAssetsDirectory
);
return fetch(request);
return workerResponse;
}
return asyncHandler();
},
Expand Down
2 changes: 0 additions & 2 deletions packages/playground/remote/src/lib/is-uploaded-file-path.ts

This file was deleted.

2 changes: 0 additions & 2 deletions packages/playground/remote/src/lib/worker-thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { WebPHP, WebPHPEndpoint, exposeAPI } from '@php-wasm/web';
import { EmscriptenDownloadMonitor } from '@php-wasm/progress';
import { setURLScope } from '@php-wasm/scopes';
import { DOCROOT, wordPressSiteUrl } from './config';
import { isUploadedFilePath } from './is-uploaded-file-path';
import {
getWordPressModule,
LatestSupportedWordPressVersion,
Expand Down Expand Up @@ -85,7 +84,6 @@ const { php, phpReady } = WebPHP.loadSync(phpVersion, {
requestHandler: {
documentRoot: DOCROOT,
absoluteUrl: scopedSiteUrl,
isStaticFilePath: isUploadedFilePath,
},
// We don't yet support loading specific PHP extensions one-by-one.
// Let's just indicate whether we want to load all of them.
Expand Down