-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add site server process * Update `getWpNowPath` to support site server process * Update webpack configuration to generate site server process bundle * Use server process in the app * Update `phpGetThemeDetails` to use server process * Ensure server uses port defined by the app * Extend logging to support forked processes * Setup logging for site server process * Improve process exit error message * Update webpack main configuration to support extra entries * Fix error case when waiting for a message response * Rename wait for response function * Rename message payload in site server process child * Update inline comments in main webpack configuration * Use handlers approach in server child process * Kill process upon server stop * Wait for server to stop
- Loading branch information
Showing
10 changed files
with
332 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { PHPRunOptions } from '@php-wasm/universal'; | ||
import { startServer, type WPNowServer } from '../../vendor/wp-now/src'; | ||
import { WPNowOptions } from '../../vendor/wp-now/src/config'; | ||
import { setupLogging } from '../logging'; | ||
import type { MessageName } from './site-server-process'; | ||
|
||
type Handler = ( message: string, messageId: number, data: unknown ) => void; | ||
type Handlers = { [ K in MessageName ]: Handler }; | ||
|
||
// Setup logging for the forked process | ||
if ( process.env.STUDIO_APP_LOGS_PATH ) { | ||
setupLogging( { | ||
processId: 'site-server-process', | ||
isForkedProcess: true, | ||
logDir: process.env.STUDIO_APP_LOGS_PATH, | ||
} ); | ||
} | ||
|
||
const options = JSON.parse( process.argv[ 2 ] ) as WPNowOptions; | ||
let server: WPNowServer; | ||
|
||
const handlers: Handlers = { | ||
'start-server': createHandler( start ), | ||
'stop-server': createHandler( stop ), | ||
'run-php': createHandler( runPhp ), | ||
}; | ||
|
||
async function start() { | ||
server = await startServer( options ); | ||
return { | ||
php: { | ||
documentRoot: server.php.documentRoot, | ||
}, | ||
}; | ||
} | ||
|
||
async function stop() { | ||
await server.stopServer(); | ||
} | ||
|
||
async function runPhp( data: unknown ) { | ||
const request = data as PHPRunOptions; | ||
if ( ! request ) { | ||
throw Error( 'PHP request is not valid' ); | ||
} | ||
const response = await server.php.run( request ); | ||
return response.text; | ||
} | ||
|
||
function createHandler< T >( handler: ( data: unknown ) => T ) { | ||
return async ( message: string, messageId: number, data: unknown ) => { | ||
try { | ||
const response = await handler( data ); | ||
process.parentPort.postMessage( { | ||
message, | ||
messageId, | ||
data: response, | ||
} ); | ||
} catch ( error ) { | ||
const errorObj = error as Error; | ||
if ( ! errorObj ) { | ||
process.parentPort.postMessage( { message, messageId, error: Error( 'Unknown Error' ) } ); | ||
return; | ||
} | ||
process.parentPort.postMessage( { | ||
message, | ||
messageId, | ||
error: errorObj, | ||
} ); | ||
} | ||
}; | ||
} | ||
|
||
process.parentPort.on( 'message', async ( { data: messagePayload } ) => { | ||
const { message, messageId, data }: { message: MessageName; messageId: number; data: unknown } = | ||
messagePayload; | ||
const handler = handlers[ message ]; | ||
if ( ! handler ) { | ||
process.parentPort.postMessage( { | ||
message, | ||
messageId, | ||
error: Error( `No handler defined for message '${ message }'` ), | ||
} ); | ||
return; | ||
} | ||
await handler( message, messageId, data ); | ||
} ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import { app, utilityProcess, UtilityProcess } from 'electron'; | ||
import { PHPRunOptions } from '@php-wasm/universal'; | ||
import { WPNowOptions } from '../../vendor/wp-now/src/config'; | ||
|
||
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack | ||
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on | ||
// whether you're running in development or production). | ||
declare const SITE_SERVER_PROCESS_MODULE_PATH: string; | ||
|
||
export type MessageName = 'start-server' | 'stop-server' | 'run-php'; | ||
|
||
const DEFAULT_RESPONSE_TIMEOUT = 25000; | ||
|
||
export default class SiteServerProcess { | ||
lastMessageId = 0; | ||
options: WPNowOptions; | ||
process?: UtilityProcess; | ||
php?: { documentRoot: string }; | ||
url: string; | ||
|
||
constructor( options: WPNowOptions ) { | ||
this.options = options; | ||
this.url = options.absoluteUrl ?? ''; | ||
} | ||
|
||
async start(): Promise< void > { | ||
return new Promise( ( resolve, reject ) => { | ||
const spawnListener = async () => { | ||
const messageId = this.sendMessage( 'start-server' ); | ||
try { | ||
const { php } = await this.waitForResponse< Pick< SiteServerProcess, 'php' > >( | ||
'start-server', | ||
messageId | ||
); | ||
this.php = php; | ||
// Removing exit listener as we only need it upon starting | ||
this.process?.off( 'exit', exitListener ); | ||
resolve(); | ||
} catch ( error ) { | ||
reject( error ); | ||
} | ||
}; | ||
const exitListener = ( code: number ) => { | ||
if ( code !== 0 ) { | ||
reject( new Error( `Site server process exited with code ${ code } upon starting` ) ); | ||
} | ||
}; | ||
|
||
this.process = utilityProcess | ||
.fork( SITE_SERVER_PROCESS_MODULE_PATH, [ JSON.stringify( this.options ) ], { | ||
serviceName: 'studio-site-server', | ||
env: { | ||
...process.env, | ||
STUDIO_SITE_SERVER_PROCESS: 'true', | ||
STUDIO_APP_NAME: app.name, | ||
STUDIO_APP_DATA_PATH: app.getPath( 'appData' ), | ||
STUDIO_APP_LOGS_PATH: app.getPath( 'logs' ), | ||
}, | ||
} ) | ||
.on( 'spawn', spawnListener ) | ||
.on( 'exit', exitListener ); | ||
} ); | ||
} | ||
|
||
async stop() { | ||
const message = 'stop-server'; | ||
const messageId = this.sendMessage( message ); | ||
await this.waitForResponse( message, messageId ); | ||
await this.#killProcess(); | ||
} | ||
|
||
async runPhp( data: PHPRunOptions ): Promise< string > { | ||
const message = 'run-php'; | ||
const messageId = this.sendMessage( message, data ); | ||
return await this.waitForResponse( message, messageId ); | ||
} | ||
|
||
sendMessage< T >( message: MessageName, data?: T ) { | ||
const process = this.process; | ||
if ( ! process ) { | ||
throw Error( 'Server process is not running' ); | ||
} | ||
|
||
const messageId = +this.lastMessageId; | ||
process.postMessage( { message, messageId, data } ); | ||
return messageId; | ||
} | ||
|
||
async waitForResponse< T = undefined >( | ||
originalMessage: MessageName, | ||
originalMessageId: number, | ||
timeout = DEFAULT_RESPONSE_TIMEOUT | ||
): Promise< T > { | ||
const process = this.process; | ||
if ( ! process ) { | ||
throw Error( 'Server process is not running' ); | ||
} | ||
|
||
return new Promise( ( resolve, reject ) => { | ||
const handler = ( { | ||
message, | ||
messageId, | ||
data, | ||
error, | ||
}: { | ||
message: MessageName; | ||
messageId: number; | ||
data: T; | ||
error?: Error; | ||
} ) => { | ||
if ( message !== originalMessage || messageId !== originalMessageId ) { | ||
return; | ||
} | ||
process.removeListener( 'message', handler ); | ||
clearTimeout( timeoutId ); | ||
if ( typeof error !== 'undefined' ) { | ||
reject( error ); | ||
return; | ||
} | ||
resolve( data ); | ||
}; | ||
|
||
const timeoutId = setTimeout( () => { | ||
reject( new Error( `Request for message ${ originalMessage } timed out` ) ); | ||
process.removeListener( 'message', handler ); | ||
}, timeout ); | ||
|
||
process.addListener( 'message', handler ); | ||
} ); | ||
} | ||
|
||
async #killProcess(): Promise< void > { | ||
const process = this.process; | ||
if ( ! process ) { | ||
throw Error( 'Server process is not running' ); | ||
} | ||
|
||
return new Promise( ( resolve, reject ) => { | ||
process.once( 'exit', ( code ) => { | ||
if ( code !== 0 ) { | ||
reject( new Error( `Site server process exited with code ${ code } upon stopping` ) ); | ||
return; | ||
} | ||
resolve(); | ||
} ); | ||
process.kill(); | ||
} ); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.