PHP: Run-Time Environment for WASD

Version 2.0.0, 1st October 2024

Copyright © 2002-2024 Mark G. Daniel
This program, comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it...

--------------------------------------------------------------------
                   The PHP License, version 3.01
    Copyright (c) 1999 - 2019 The PHP Group. All rights reserved.
--------------------------------------------------------------------

Redistribution and use in source and binary forms, with or without
modification, is permitted provided that the following conditions
are met:

  1. Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.

  8< snip 8<
https://www.php.net/license/3_01.txt

Contents




This is an interface to a PHP interpreter engine and environment for the WASD OpenVMS Web server. It is designed to be able to be used in standard CGI and CGIplus/RTE persistent scripting modes. The persistent modes provide substantial improvement in script activation times and reduced load on server and system. phpRTE cannot be used interactively or at the command line, it is only for scripting use.

This version 2 of phpRTE has been tailored to the VMS Software official PHP releases, version 8 and later.

https://vmssoftware.com/products/php/

Where previous phpRTE versions have been installed it is strongly suggested the existing phpRTE kit be renamed out of the way (as described in step 2 below) before installing this kit so there can be no interactions between kit contents. After successful installation and testing the previous directory trees may be deleted.

The WASD kit supplies the RTE source code, DCL installation and startup procedures, some elementary scripts, and this document — the WASD infrastructure.

Installation

  1. Obtain the phpRTE kit from
    http://wasd.vsm.com.au/wasd/
  2. It is suggested that any existing phpRTE kit be renamed out of the way before installing this kit so there can be no interactions between kit contents. After successful installation the previous directories may be deleted.
    $ RENAME WASD_ROOT:[000000]PHPRTE.DIR WASD_ROOT:[000000]PHPRTE_nnn.DIR
    

  3. UNZIP the phpRTE archive
    $ SET DEFAULT WASD_ROOT:[000000]
    $ UNZIP location:PHPRTE200.ZIP
    

  4. Install the WASD infrastructure and PHP engine
    $ SET DEFAULT WASD_ROOT:[SRC.PHPRTE]
    $ @BUILD_PHPRTE [BUILD|LINK]
    $ COPY WASD_EXE:PHPRTE.EXE CGI_EXE:
    $ COPY PHPRTE.COM CGI_BIN:
    

  5. Configure the Web server

  6. Configure the PHP environment

  7. Start scripting  :^)  Try the example scripts.

    $ COPY WASD_ROOT:[SRC.phpRTE.SCRIPTS]*.PHP CGI_BIN:
    

  8. Also see Mark Berryman PHP

Configure WASD

There are various ways to employ the WASD PHP interpreter. It can be used in vanilla CGI mode (not recommended), or in persistent CGIplus/RTE mode. Benchmarking indicates the CGIplus/RTE use reduces activation time to 10% of CGI (yes, an order of magnitude). There are subtle differences in the way CGIplus and RTE parse and provide the PATH_INFO data. See the "WASD Scripting" document for more detail.

Configuration and Mapping

One or more of the following approaches can be implemented.

PHP Applications

A PHP application is a package of PHP scripts and associated resources such as style-sheets, icons and other non-script files, in a single directory tree, often with various elements collected together, such as icons and scripts, but sometimes with multiple instances of these or with them interspersed amongst each other. That is, as a complex mix of file purposes.

The following rule allows .php type files anywhere in the application tree to be executed. Deploy exercising due diligence. The script=as= is optional but the recommended approach for the reasons provided above. Note immediately following the exec rule is a pass rule providing access to all the non-script resources associated with the application.

# WASD_CONFIG_MAP
exec+ /app/**.php* (@cgi-bin:[000000]phprte.com)/app_root/*.php* \
      script=query=relaxed map=once ods=5 script=as=APP_ACCOUNT
pass /app/* /app_root/*

The WASD serving model uses two independent accounts, one for more static resources and the other for overtly dynamic content, that is scripts, and is considered the minimum best practise from a security perspective. A breach in the scripting environment has less chance to compromise or damage the server core, and vice versa. Better practise is an account for every major processing system (web-based application) handled by the server. In either case the multiple accounts needing to access the application tree potentially complicates the file protection requirements.

With an interpreter such as PHP, the scripting engine, and therefore the scripting account, requires only read access to the source files and directories. If the application additionally needs to write into some portion of the file-system then of course this needs to be carefully considered and implemented. At the very least the server account requires read access to the directories so that the script may be located before activating the scripting engine. If the application tree contains non-script resources to be served then they also require read access for the server account.

There are three basic approaches. The examples are illustrative only.

  1. Other considerations ignored, the simplest is to make the directory tree WORLD READ (with or without propagation ACL).
    APP.DIR;1             0.50KB    1-FEB-2014 05:16  [SYSTEM]
      (RWED,RWED,R,R)
              (DEFAULT_PROTECTION,SYSTEM:RWED,OWNER:RWED,GROUP:R,WORLD:R)
    
    EXAMPLE.PHP;1            1KB    1-FEB-2014 05:16  [SYSTEM]
      (RWED,RWED,R,R)
    
  2. The second involves an ACL that provides access to the specific accounts involved in serving up the application (this is the WASD model).
    APP.DIR;1             0.50KB    1-FEB-2014 05:16  [SYSTEM]
            (RWED,RWED,,)
              (IDENTIFIER=WASD_HTTP_SERVER,ACCESS=READ)
              (IDENTIFIER=WASD_HTTP_NOBODY,ACCESS=READ)
              (IDENTIFIER=*,ACCESS=NONE)
              (IDENTIFIER=WASD_HTTP_NOBODY,OPTIONS=DEFAULT,ACCESS=READ)
              (IDENTIFIER=*,OPTIONS=DEFAULT,ACCESS=NONE)
              (DEFAULT_PROTECTION,SYSTEM:RWED,OWNER:RWED,GROUP:,WORLD:)
    
    EXAMPLE.PHP;1            1KB    1-FEB-2014 05:16  [SYSTEM]
            (RWED,RWED,,)
              (IDENTIFIER=WASD_HTTP_SERVER,ACCESS=READ)
              (IDENTIFIER=WASD_HTTP_NOBODY,ACCESS=READ)
    
  3. The third involves creating a rights identifier, granting that to both the server and scripting(/application) accounts, and then applying an ACL to the directory tree providing READ access to that rights identifier.
    APP.DIR;1             0.50KB    1-FEB-2014 05:16  [SYSTEM]
            (RWED,RWED,,)
              (IDENTIFIER=APP_ACCESS,ACCESS=READ)
              (IDENTIFIER=*,ACCESS=NONE)
              (IDENTIFIER=APP_ACCESS,OPTIONS=DEFAULT,ACCESS=READ)
              (IDENTIFIER=*,OPTIONS=DEFAULT,ACCESS=NONE)
              (DEFAULT_PROTECTION,SYSTEM:RWED,OWNER:RWED,GROUP:,WORLD:)
    
    EXAMPLE.PHP;1            1KB    1-FEB-2014 05:16  [SYSTEM]
            (RWED,RWED,,)
              (IDENTIFIER=APP_ACCESS,ACCESS=READ)
              (IDENTIFIER=*,ACCESS=NONE)
    

No PHP or Zend Logos?

By default WASD validates query strings in its own inimitable fashion (correctly in the author's HO). This validation interferes with the requesting of the internally generated PHP and Zend logos (as are included by the php_info.php script) and may similarly for other PHP scripts. To disable query string validation by WASD include the following WASD_CONFIG_MAP rule at an appropriate location before or at mapping of PHP scripts.

set /**.php* script=query=relaxed

PHP on ODS-5 Volumes

For scripts requiring extended file specification (EFS, located on ODS-5 volumes) the script path needs to be mapped as ODS-5.

set /**.php* script=query=relaxed ods=5

When a script environment is mapped as residing on an ODS-5 volume, any VMS-syntax file specifications contained in PATH_TRANSLATED and SCRIPT_NAME are automatically translated to Unix-style syntax. This has been demonstrated to better support PHP applications derived from such environments, in particular allowing relative directory references. This occurs whether or not the path is SET using script=syntax=unix.

For example; by default a request's underlying CGI variables might be:

REQUEST_URI /php-bin/php_info.php/wasd_root/src/phprte/
PATH_INFO /wasd_root/src/phprte/
PATH_TRANSLATED WASD_ROOT:[SRC.PHPRTE]
SCRIPT_NAME /php-bin/php_info.php
SCRIPT_FILENAME PHP-BIN:[000000]PHP_INFO.PHP

After mapping being on ODS-5 and subsequent translation they are presented as:

_SERVER["REQUEST_URI"] /php-bin/php_info.php/wasd_root/src/phprte/
_SERVER["PATH_INFO"] /wasd_root/src/phprte/
_SERVER["PATH_TRANSLATED"] /WASD_ROOT/SRC/PHPRTE/
_SERVER["SCRIPT_NAME"] /php-bin/php_info.php
_SERVER["SCRIPT_FILENAME"] /PHP-BIN/000000/PHP_INFO.PHP

Script Default or Home Directory

The phpRTE engine will by default chdir() to the Unix-style syntax equivalent of the directory containing the PHP script file. It also does a setenv() against the environment variable PATH to this same string. This location may be explicitly provided using the value of CGI variable SCRIPT_DEFAULT and set on a per-script or general basis using the mapping rule Set script=default=<string>. It will accept either VMS and Unix specifications depending on the requirements of the script itself.

set /php-bin/mumble.php* script=default="/mumble_device/000000"
set /php-bin/mumble.php* script=default="mumble_device:[000000]"

"Watch" Mode

This is a basic facility to assist in debugging the interactions between the WASD server, phpRTE scripting engine, script activation and input/output. It does not provide for debugging of the script itself but may be of some elementary assistance when investigating the environment the script is activating under. There are a number of methods for activating "watch" mode.

When either of the logical names is defined without the other each operates to enable "watch" completely independently. When defined concurrently both must match for "watch" to be enabled. For example; when PHPRTE$WATCH_SCRIPT_NAME is defined only that script is "watch"ed; when PHPRTE$WATCH_REMOTE_ADDR is defined, all scripts activated by the specified host are "watch"ed; when both are defined only the specified script can be "watch"ed by the specified host. Similar (but different :-) constraints apply to the script-embedded string.

A WATCH statement contains a statement number, timestamp, and then some free-form text (that hopefully is self-explanatory). WATCH output can also comprise a hex-dump of a block of data.

Configure PHP

The author of phpRTE is (still) only a PHP novice, so anything in this section should be taken with a large pinch of salt. Any scripting environment should be approached with due caution and diligence. Please ensure you are familiar with PHP and its security requirements in particular before betting the company on anything in this section.

PHP configuration is accomplished using the PHP.INI file, by default located in the PHP_ROOT:[000000] directory.

Mapping rules configure on a per-request basis. Logical names and configuration file content are loaded once at persistent phpRTE engine startup. Changes to configuration need to be loaded into any instantiated PHP engine(s) using $HTTPD/DO=DCL=PURGE.

PHPRTE$INI

The PHP engine has a set of default configuration parameters and so can be used without specific configuration. This not a tutorial on which changes should be made for any give circumstance, just how to pass those changes into the phpRTE scripting engine. To change the defaults a configuration file PHP.INI should be provided. The default location of this for VMS PHP and WASD is PHP_ROOT:[000000]PHP.INI. To use a PHP.INI  from a location other than the VMS PHP default define the logical name PHPRTE$INI to locate the file. This logical name can be in any table the scripting process can access (e.g. process, job, system).

PHPRTE$INI2

phpRTE also has a secondary PHP.INI which contains directives that override those from the primary PHP.INI. This can be useful when maintaining the site-wide PHP configuration in the primary PHP.INI while placing only those directives required to be added or modified for the particular application. To have the phpRTE engine process this file define the logical name PHPRTE$INI2 to contain the location of the file. This can be defined in any table the scripting process can access (e.g. process, job, system). To suppress use of the primary PHP.INI file and rely entirely on the secondary for configuration define the primary logical name to the NL: device.

$ DEFINE PHPRTE$INI NL:

PHPRTE$INIS

To specify PHP.INI directives directly (and avoid the overhead of file processing) the logical name PHPRTE$INIS can be defined. The format for this string is name="value"[;name="value"], where a semicolon is used to separate directives. Any directive used in this logical overrides the same directive in the primary and secondary PHP.INI. This is an example of such a logical value string and its definition (continued for printed page clarity).

short_open_tag=1 ; expose_php=0 ; include_path="/phprte/application3/include"

$ DEFINE PHPRTE$INIS "short_open_tag=1 ; expose_php=0 ; include_path=""/phprte/application3/include"""

Mapping Rules

It is possible to pass parameters to PHP scripts and applications using the script=param=(name=value) mapping rule. The phpRTE scripting engine checks for a number of reserved identifiers and will use the contents of those present to perform the indicated function. The primary PHP.INI cannot be supplied via mapping rule due to PHP module startup requirements.

Parameter Name Purpose
PHP_INI Provides the location for the primary PHP.INI configuration file.
PHP_INI2 Provides the location for the secondary PHP.INI configuration file.
PHP_INIS Allows the specification of a value for any of the configuration directives allowed in PHP.INI. The format for these parameters is name="value"[;name="value"], where a semi-colon is used to separate directives. See examples below.

The first example shows the specification of a primary PHP.INI file, the second of a secondary PHP.INI file, and the third the use of a configuration directive string.

set /phprte/application_a/* script=param=(PHP_INI="php:[application_a]php.ini")

set /phprte/application_b/* script=param=(PHP_INI2="php:[application_b]php.ini")

# continuation lines used for printed page clarity
set /phprte/application_c* script=param=(PHP_INIS=\
'short_open_tag=1 ; expose_php=0 ; include_path="/phprte/application_c/include"')

Example Scripts

After configuration the following scripts may be used to confirm the environment is functioning.

Script sources:  WASD_ROOT:[SRC.PHPRTE.SCRIPTS]

Performance

The performance of phpRTE is quite respectable.

This test bench was an innotek GmbH VirtualBox "Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz" with 3 CPUs and 7GB running VMS V9.2-2 and VSI TCP/IP Services for OpenVMS x86_64 Version V6.0.

X86VMS$ @kits:vups
innotek GmbH VirtualBox with 3 CPU and 7936MB running VMS V9.2-2
Approximate System VUPs Rating : 653.4 ( min: 653.4 max: 653.4 )
The data have been collected using the h2load utility (https://nghttp2.org/documentation/h2load.1.html) from the HTTP/2 C Library (https://nghttp2.org). This utility can be used to configurably load HTTP, HTTPS and HTTP/2 servers. Note that the number of client threads (-t) is explicitly set to the connection concurrency (-c) to maximise h2load processing. The h2load utility maintains connection persistence between HTTP/1.1 requests so the network connection setup is generally only a factor for the first of many.
% h2load --h1 -n 1000 -c 2 -t 2 https://x86vms.lan/cgi-bin/php_info.php
starting benchmark...
spawning thread #0: 1 total client(s). 500 total requests
spawning thread #1: 1 total client(s). 500 total requests
TLS Protocol: TLSv1.3
Cipher: TLS_AES_256_GCM_SHA384
Server Temp Key: X25519 253 bits
Application protocol: http/1.1
progress: 10% done
 8< snip 8<
progress: 100% done

finished in 8.62s, 115.95 req/s, 6.17MB/s
requests: 1000 total, 1000 started, 1000 done, 1000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 1000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 53.20MB (55783147) total, 265.63KB (272000) headers (space savings 0.00%), 52.72MB (55280147) data
                     min         max         mean         sd        +/- sd
time for request:    11.62ms    119.16ms     16.96ms      7.93ms    99.10%
time for connect:    30.91ms     50.70ms     40.80ms     13.99ms   100.00%
time to 1st byte:    66.03ms     69.92ms     67.98ms      2.75ms   100.00%
req/s           :      57.98       59.36       58.67        0.98   100.00%

And for the simpler script (with far less output):

% h2load --h1 -n 1000 -c 2 -t 2 https://x86vms.lan/cgi-bin/php_rules.php
starting benchmark...
 8< snip 8<
finished in 3.93s, 254.49 req/s, 130.97KB/s
requests: 1000 total, 1000 started, 1000 done, 1000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 1000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 514.65KB (527000) total, 265.63KB (272000) headers (space savings 0.00%), 175.78KB (180000) data
                     min         max         mean         sd        +/- sd
time for request:     7.23ms     38.63ms      7.77ms      1.13ms    98.20%
time for connect:    29.92ms     50.24ms     40.08ms     14.37ms   100.00%
time to 1st byte:    68.54ms     72.45ms     70.49ms      2.77ms   100.00%
req/s           :     127.25      127.39      127.32        0.10   100.00%

Apache/2.4.54 (OpenVMS) PHP/8.0.10 OpenSSL/3.0.12

% h2load --h1 -n 1000 -c 2 -t 2 https://x86vms.lan:7443/php_info.php
starting benchmark...
spawning thread #0: 1 total client(s). 500 total requests
spawning thread #1: 1 total client(s). 500 total requests
TLS Protocol: TLSv1.3
Cipher: TLS_AES_128_GCM_SHA256
Server Temp Key: X25519 253 bits
Application protocol: http/1.1
progress: 10% done
 8< snip 8<
progress: 100% done

finished in 6.38s, 156.80 req/s, 9.42MB/s
requests: 1000 total, 1000 started, 1000 done, 1000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 1000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 60.08MB (62997112) total, 166.13KB (170120) headers (space savings 0.00%), 59.87MB (62775000) data
                     min         max         mean         sd        +/- sd
time for request:    10.32ms     21.14ms     12.33ms      1.30ms    78.80%
time for connect:    30.51ms     32.19ms     31.35ms      1.19ms   100.00%
time to 1st byte:    42.77ms     44.63ms     43.70ms      1.31ms   100.00%
req/s           :      78.40       79.87       79.14        1.04   100.00%

And again for the simpler script (with far less output):

% h2load --h1 -n 1000 -c 2 -t 2 https://x86vms.lan:7443/php_rules.php
starting benchmark...
 8< snip 8<
finished in 4.12s, 242.79 req/s, 94.08KB/s
requests: 1000 total, 1000 started, 1000 done, 1000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 1000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 387.50KB (396800) total, 166.13KB (170120) headers (space savings 0.00%), 175.78KB (180000) data
                     min         max         mean         sd        +/- sd
time for request:     7.07ms     19.25ms      7.93ms      1.22ms    94.80%
time for connect:    26.04ms     26.47ms     26.25ms       304us   100.00%
time to 1st byte:    37.83ms     39.67ms     38.75ms      1.30ms   100.00%
req/s           :     121.40      122.09      121.74        0.48   100.00%

DCLsymbol Extension

A practical solution to a WASD site requiring data passback from a PHP script to a wrapping DCL procedure. This extension allows DCL local and global symbols to be assigned, deleted, and values accessed from within PHPRTE.

Further information is available in the source code of DCLSYMBOL.C

Mark Berryman PHP

To customise WASD phpRTE for a Mark Berryman (MB) release.

•  phpRTE has a different structure to previous MB expectations.
•  The MB PHP kit is integrated into the the WASD scripting (cgi-bin) structure.
•  MB PHP executables are placed into the CGI_EXE directory.
•  MB PHP extensions are placed into an [.EXTENSIONS] subdirectory of the CGI_EXE directory.
•  A wrapper procedure massages the MB expectations (from PHPWASD.EXE days) into VSI PHP directory expectations.
  1. Obtain a relevant kit from
    https://www.theberrymans.com/php_kits/

  2. Suggest creating a version-specific directory for the kit inside the WASD directory structure (simplfies the copying files).
    $ create /directory [src.php_x86_8_1_23]
    $ set default [src.php_x86_8_1_23]
    $ unzip <location>php_x86_8_1_23.zip
    

  3. Then integrating the MB executables into the scripting environment.
    $ extensions = f$trnlnm("cgi_exe") - "]" + ".extensions]"
    $ create /directory 'extensions'
    $ copy [src.php_x86_8_1_23]phprte.exe,php_cgi.exe,phpdbg.exe,phpshr.exe,phpwasd.exe cgi_exe:
    $ copy /exclude=(php_cgi.exe) [src.php_x86_8_1_23]php_*.exe 'extensions'
    $ httpd /do=dcl=purge
    

To remove MB PHP environment or before upgrading.

$ extensions = f$trnlnm("cgi_exe") - "]" + ".extensions]"
$ delete cgi_exe:phprte.exe;*,php_cgi.exe;*,phpdbg.exe;*,phpshr.exe;*,phpwasd.exe;*
$ delete 'extensions'php_*.exe;*
$ delete cgi_exe:extensions.dir;*
$ httpd /do=dcl=purge

Problems?

Unfortunately the author of the phpRTE interface is such a PHP novice he is not in any position to answer queries about PHP "programming" or usage. If there's an obvious behavioural problem with phpRTE (preferably diagnosed by someone with PHP experience) then he's it though.

Releases

v2.0.0  01-OCT-2024
•  VSI PHP 8
•  reframe PHPWASD as phpRTE
• [src.phprte] and now [src.phprte] allows concurrent support for previous release(s)
v1.4.5  02-FEB-2014
•  tweak for PHP 5.4.n (future release at this date)
v1.0.0  16-JAN-2002
•  initial

Acknowlegements

VMS Software for their official, supported port.
Of course, thanks to the PHP development team!