Skip to content

Commit

Permalink
Playground Boot: Align the boot process between remote.html and CLI (#…
Browse files Browse the repository at this point in the history
…1389)

Aligns the boot process between the in-browser Playground Remote and
Node-oriented Playground CLI.

With this PR, both apps use a similar `createPHP()` function that:

* Sets up the SAPI name
* Sets up PHP ini entries
* Sets up the `/phpinfo.php` route
* Sets up platform-level mu0plugins
* Proxies filesystem directories from secondary PHP instances to primary
* Sets up PHP runtime rotation to avoid OOM errors in long-running
primary processes

There are still the following discrepancies:

* The in-browser PHP sets up a SAPI name conditionally, Node.js one
always uses `cli` (it probably shouldn't)
* The in-browser PHP uses a custom spawn handler
* The in-browser PHP uses a different set of php.ini directives
* The in-browser PHP loads more mu-plugins
* The Node.js PHP sets up CA certificates for HTTPS connections (the
in-browser PHP [will fake the CA chain
eventually](#1093))

This is the first step towards a consistent Boot Protocol, see
#1379 for
more details.

## Testing Instructions

* Confirm the CI checks work
* Run `bun packages/playground/cli/src/cli.ts server --login`, confirm
the server starts without issues, test wp-admin and HTTPS-reliant
features like the plugin directories.

We'll need a set of unit tests for these new boot-related features,
let's create them sooner than later.
  • Loading branch information
adamziel authored May 15, 2024
1 parent 2d20b5f commit a52a716
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 246 deletions.
23 changes: 8 additions & 15 deletions packages/playground/cli/src/setup-php.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import {
} from '@php-wasm/universal';
import { rootCertificates } from 'tls';
import { dirname } from '@php-wasm/util';
import { envPHP_to_loadMuPlugins } from '@wp-playground/wordpress';
import {
preloadPhpInfoRoute,
enablePlatformMuPlugins,
preloadRequiredMuPlugin,
} from '@wp-playground/wordpress';

export async function createPhp(
requestHandler: PHPRequestHandler<NodePHP>,
Expand Down Expand Up @@ -44,20 +48,9 @@ export async function createPhp(
'/internal/shared/ca-bundle.crt',
rootCertificates.join('\n')
);
php.writeFile(
'/internal/shared/preload/env.php',
envPHP_to_loadMuPlugins
);
php.writeFile(
'/internal/shared/preload/phpinfo.php',
`<?php
// Render PHPInfo if the requested page is /phpinfo.php
if ( '/phpinfo.php' === $_SERVER['REQUEST_URI'] ) {
phpinfo();
exit;
}`
);
php.mkdir('/internal/shared/mu-plugins');
await preloadPhpInfoRoute(php);
await enablePlatformMuPlugins(php);
await preloadRequiredMuPlugin(php);
} else {
/**
* @TODO: Consider an API similar to
Expand Down
20 changes: 6 additions & 14 deletions packages/playground/cli/src/setup-wp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ import {
readAsFile,
} from './download';
import { withPHPIniValues } from './setup-php';
import {
playgroundMuPlugin,
preloadSqliteIntegration,
} from '@wp-playground/wordpress';
import { preloadSqliteIntegration } from '@wp-playground/wordpress';

/**
* Ensures a functional WordPress installation in php document root.
Expand Down Expand Up @@ -52,9 +49,11 @@ export async function setupWordPress(
monitor
),
]);

await prepareWordPress(php, wpZip);
await preloadSqliteIntegration(php, sqliteZip);
// Setup the SQLite integration if no custom database drop-in is present
if (!php.fileExists('/wordpress/wp-content/db.php')) {
await preloadSqliteIntegration(php, sqliteZip);
}

const preinstalledWpContentPath = path.join(
CACHE_FOLDER,
Expand Down Expand Up @@ -101,7 +100,7 @@ export async function setupWordPress(
* the sqlite-database-integration zip file.
*
* This is a TypeScript function for now, just to get something off the
* ground, but it will be superseded by the PHP Blueprints library developed
* ground, but it may be superseded by the PHP Blueprints library developed
* at https://github.com/WordPress/blueprints-library/
*
* That PHP library will come with a set of functions and a CLI tool to
Expand All @@ -111,13 +110,6 @@ export async function setupWordPress(
* as that's viable.
*/
async function prepareWordPress(php: NodePHP, wpZip: File) {
php.mkdir('/internal/shared/mu-plugins');
php.writeFile(
'/internal/shared/mu-plugins/0-playground.php',
playgroundMuPlugin
);

// Extract WordPress {{{
php.mkdir('/tmp/unzipped-wordpress');
await unzip(php, {
zipFile: wpZip,
Expand Down
63 changes: 6 additions & 57 deletions packages/playground/remote/src/lib/worker-thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,8 @@ import {
LatestSupportedWordPressVersion,
SupportedWordPressVersions,
} from '@wp-playground/wordpress-builds';
import {
envPHP_to_loadMuPlugins,
playgroundMuPlugin,
wordPressRewriteRules,
} from '@wp-playground/wordpress';
import {
PHPRequestHandler,
proxyFileSystem,
writeFiles,
} from '@php-wasm/universal';
import { wordPressRewriteRules } from '@wp-playground/wordpress';
import { PHPRequestHandler } from '@php-wasm/universal';
import {
SyncProgressCallback,
bindOpfs,
Expand All @@ -28,13 +20,7 @@ import {
unzip,
} from '@wp-playground/blueprints';

/** @ts-ignore */
import transportFetch from './playground-mu-plugin/playground-includes/wp_http_fetch.php?raw';
/** @ts-ignore */
import transportDummy from './playground-mu-plugin/playground-includes/wp_http_dummy.php?raw';
/** @ts-ignore */
import playgroundWebMuPlugin from './playground-mu-plugin/0-playground.php?raw';
import { joinPaths, randomString } from '@php-wasm/util';
import { randomString } from '@php-wasm/util';
import {
requestedWPVersion,
createPhp,
Expand Down Expand Up @@ -169,37 +155,12 @@ export class PlaygroundWorkerEndpoint extends WebPHPEndpoint {

const scopedSiteUrl = setURLScope(wordPressSiteUrl, scope).toString();
const requestHandler = new PHPRequestHandler({
phpFactory: async ({ isPrimary }) => {
const php = await createPhp(requestHandler);
php.defineConstant('SCOPED_SITE_PATH', new URL(scopedSiteUrl).pathname);
if (isPrimary) {
php.writeFile(
'/internal/shared/preload/env.php',
envPHP_to_loadMuPlugins
);
php.writeFile(
'/internal/shared/preload/phpinfo.php',
`<?php
// Render PHPInfo if the requested page is /phpinfo.php
if ( SCOPED_SITE_PATH . '/phpinfo.php' === $_SERVER['REQUEST_URI'] ) {
phpinfo();
exit;
}
`
);
} else {
proxyFileSystem(await requestHandler.getPrimaryPhp(), php, [
'/tmp',
requestHandler.documentRoot,
'/internal/shared',
]);
}
return php;
},
phpFactory: async ({ isPrimary }) =>
await createPhp(requestHandler, scopedSiteUrl, isPrimary),
documentRoot: DOCROOT,
absoluteUrl: scopedSiteUrl,
rewriteRules: wordPressRewriteRules,
});
}) as PHPRequestHandler<WebPHP>;
const apiEndpoint = new PlaygroundWorkerEndpoint(
requestHandler,
downloadMonitor,
Expand Down Expand Up @@ -241,18 +202,6 @@ try {
});
}

// Always install the playground mu-plugin, even if WordPress is loaded
// from the OPFS. This ensures:
// * The mu-plugin is always there, even when a custom WordPress directory
// is mounted.
// * The mu-plugin is always up to date.
await writeFiles(primaryPhp, joinPaths('/internal/shared/mu-plugins'), {
'0-playground.php': playgroundMuPlugin,
'1-playground-web.php': playgroundWebMuPlugin,
'playground-includes/wp_http_dummy.php': transportDummy,
'playground-includes/wp_http_fetch.php': transportFetch,
});

if (virtualOpfsDir) {
await bindOpfs({
php: primaryPhp,
Expand Down
46 changes: 43 additions & 3 deletions packages/playground/remote/src/lib/worker-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,24 @@ import {
SupportedPHPVersionsList,
rotatePHPRuntime,
PHPRequestHandler,
proxyFileSystem,
writeFiles,
} from '@php-wasm/universal';
import { EmscriptenDownloadMonitor } from '@php-wasm/progress';
import { createSpawnHandler, phpVar } from '@php-wasm/util';
import { createSpawnHandler, joinPaths, phpVar } from '@php-wasm/util';
import { createMemoizedFetch } from './create-memoized-fetch';
import { logger } from '@php-wasm/logger';
/** @ts-ignore */
import transportFetch from './playground-mu-plugin/playground-includes/wp_http_fetch.php?raw';
/** @ts-ignore */
import transportDummy from './playground-mu-plugin/playground-includes/wp_http_dummy.php?raw';
/** @ts-ignore */
import playgroundWebMuPlugin from './playground-mu-plugin/0-playground.php?raw';
import {
enablePlatformMuPlugins,
preloadPhpInfoRoute,
preloadRequiredMuPlugin,
} from '@wp-playground/wordpress';

export type ReceivedStartupOptions = {
wpVersion?: string;
Expand Down Expand Up @@ -59,15 +72,42 @@ export const startupOptions = {
phpExtensions: receivedParams.phpExtensions || [],
} as ParsedStartupOptions;

export async function createPhp(requestHandler: PHPRequestHandler<WebPHP>) {
export async function createPhp(
requestHandler: PHPRequestHandler<WebPHP>,
siteUrl: string,
isPrimary: boolean
) {
const php = new WebPHP();
php.requestHandler = requestHandler as any;

php.initializeRuntime(await createPhpRuntime());
php.setPhpIniEntry('memory_limit', '256M');
if (startupOptions.sapiName) {
await php.setSapiName(startupOptions.sapiName);
}
php.setPhpIniEntry('memory_limit', '256M');
php.setSpawnHandler(spawnHandlerFactory(requestHandler.processManager));

if (isPrimary) {
const scopedSitePath = new URL(siteUrl).pathname;
await preloadPhpInfoRoute(
php,
joinPaths(scopedSitePath, 'phpinfo.php')
);
await enablePlatformMuPlugins(php);
await preloadRequiredMuPlugin(php);
await writeFiles(php, joinPaths('/internal/shared/mu-plugins'), {
'1-playground-web.php': playgroundWebMuPlugin,
'playground-includes/wp_http_dummy.php': transportDummy,
'playground-includes/wp_http_fetch.php': transportFetch,
});
} else {
proxyFileSystem(await requestHandler.getPrimaryPhp(), php, [
'/tmp',
requestHandler.documentRoot,
'/internal/shared',
]);
}

// Rotate the PHP runtime periodically to avoid memory leak-related crashes.
// @see https://github.com/WordPress/wordpress-playground/pull/990 for more context
rotatePHPRuntime({
Expand Down
Loading

0 comments on commit a52a716

Please sign in to comment.