diff --git a/packages/php-wasm/node/src/test/php-request-handler.spec.ts b/packages/php-wasm/node/src/test/php-request-handler.spec.ts index e5358aea3b..da46e81f62 100644 --- a/packages/php-wasm/node/src/test/php-request-handler.spec.ts +++ b/packages/php-wasm/node/src/test/php-request-handler.spec.ts @@ -177,6 +177,66 @@ describe.each(SupportedPHPVersions)( }); }); + it('should return httpStatus 500 if exit code is not 0', async () => { + php.writeFile( + '/index.php', + ` { /** * Tests against calling phpwasm_init_uploaded_files_hash() when diff --git a/packages/php-wasm/universal/src/lib/base-php.ts b/packages/php-wasm/universal/src/lib/base-php.ts index d3cde60cdc..e5632bdd22 100644 --- a/packages/php-wasm/universal/src/lib/base-php.ts +++ b/packages/php-wasm/universal/src/lib/base-php.ts @@ -33,6 +33,17 @@ const STRING = 'string'; const NUMBER = 'number'; export const __private__dont__use = Symbol('__private__dont__use'); + +export class PHPExecutionFailureError extends Error { + constructor( + message: string, + public response: PHPResponse, + public source: 'request' | 'php-wasm' + ) { + super(message); + } +} + /** * An environment-agnostic wrapper around the Emscripten PHP runtime * that universals the super low-level API and provides a more convenient @@ -278,14 +289,12 @@ export abstract class BasePHP implements IsomorphicLocalPHP { const response = await this.#handleRequest(); if (response.exitCode !== 0) { console.warn(`PHP.run() output was:`, response.text); - const error = new Error( + const error = new PHPExecutionFailureError( `PHP.run() failed with exit code ${response.exitCode} and the following output: ` + - response.errors - ); - // @ts-ignore - error.response = response; - // @ts-ignore - error.source = 'request'; + response.errors, + response, + 'request' + ) as PHPExecutionFailureError; console.error(error); throw error; } @@ -661,7 +670,7 @@ export abstract class BasePHP implements IsomorphicLocalPHP { const { headers, httpStatusCode } = this.#getResponseHeaders(); return new PHPResponse( - httpStatusCode, + exitCode === 0 ? httpStatusCode : 500, headers, this.readFileAsBuffer('/internal/stdout'), this.readFileAsText('/internal/stderr'), diff --git a/packages/php-wasm/universal/src/lib/php-request-handler.ts b/packages/php-wasm/universal/src/lib/php-request-handler.ts index 3ced1a94b8..a765b887b4 100644 --- a/packages/php-wasm/universal/src/lib/php-request-handler.ts +++ b/packages/php-wasm/universal/src/lib/php-request-handler.ts @@ -5,7 +5,11 @@ import { removePathPrefix, DEFAULT_BASE_URL, } from './urls'; -import { BasePHP, normalizeHeaders } from './base-php'; +import { + BasePHP, + PHPExecutionFailureError, + normalizeHeaders, +} from './base-php'; import { PHPResponse } from './php-response'; import { PHPRequest, PHPRunOptions, RequestHandler } from './universal-php'; import { encodeAsMultipart } from './encode-as-multipart'; @@ -242,17 +246,25 @@ export class PHPRequestHandler implements RequestHandler { ); } - return await this.php.run({ - relativeUri: ensurePathPrefix( - toRelativeUrl(requestedUrl), - this.#PATHNAME - ), - protocol: this.#PROTOCOL, - method: request.method || preferredMethod, - body, - scriptPath, - headers, - }); + try { + return await this.php.run({ + relativeUri: ensurePathPrefix( + toRelativeUrl(requestedUrl), + this.#PATHNAME + ), + protocol: this.#PROTOCOL, + method: request.method || preferredMethod, + body, + scriptPath, + headers, + }); + } catch (error) { + const executionError = error as PHPExecutionFailureError; + if (executionError?.response) { + return executionError.response; + } + throw error; + } } finally { release(); } diff --git a/packages/php-wasm/web/src/lib/api.ts b/packages/php-wasm/web/src/lib/api.ts index 3dd0d89798..836b270259 100644 --- a/packages/php-wasm/web/src/lib/api.ts +++ b/packages/php-wasm/web/src/lib/api.ts @@ -160,8 +160,8 @@ function setupTransferHandlers() { }, }); // Augment Comlink's throw handler to include Error the response and source - // information in the serialized error object. BasePHP throws may include - // those information and we'll want to display them for the user. + // information in the serialized error object. BasePHP may throw PHPExecutionFailureError + // which includes those information and we'll want to display them for the user. const throwHandler = Comlink.transferHandlers.get('throw')!; const originalSerialize = throwHandler?.serialize; throwHandler.serialize = ({ value }: any) => { diff --git a/packages/php-wasm/web/src/lib/register-service-worker.ts b/packages/php-wasm/web/src/lib/register-service-worker.ts index d7a5b281ed..2fa3cb0610 100644 --- a/packages/php-wasm/web/src/lib/register-service-worker.ts +++ b/packages/php-wasm/web/src/lib/register-service-worker.ts @@ -2,7 +2,6 @@ import { PhpWasmError } from '@php-wasm/util'; import type { WebPHPEndpoint } from './web-php-endpoint'; import { responseTo } from '@php-wasm/web-service-worker'; import { Remote } from 'comlink'; -import { PHPResponse } from '@php-wasm/universal'; /** * Run this in the main application to register the service worker or @@ -63,21 +62,7 @@ export async function registerServiceWorker< const args = event.data.args || []; const method = event.data.method as keyof Client; - let result; - try { - result = await (phpApi[method] as Function)(...args); - } catch (e) { - if ( - method === 'request' && - e && - typeof e === 'object' && - 'response' in e - ) { - result = e.response as PHPResponse; - } else { - throw e; - } - } + const result = await (phpApi[method] as Function)(...args); event.source!.postMessage(responseTo(event.data.requestId, result)); } );