An open API service indexing awesome lists of open source software.

https://github.com/cubiclesoft/php-app-server

Create lightweight, installable applications written in HTML, CSS, Javascript, and PHP for the Windows, Mac, and Linux desktop operating systems.
https://github.com/cubiclesoft/php-app-server

app-server inno-setup linux-desktop mac-osx php-framework windows wix-toolset

Last synced: 2 months ago
JSON representation

Create lightweight, installable applications written in HTML, CSS, Javascript, and PHP for the Windows, Mac, and Linux desktop operating systems.

Awesome Lists containing this project

README

        

PHP App Server
==============

Create lightweight, installable applications written in HTML, CSS, Javascript, and PHP for the Windows, Mac, and Linux desktop operating systems.

![Screenshot of a generated installer on Windows](https://user-images.githubusercontent.com/1432111/46253522-88f58200-c432-11e8-97c0-8337b2d181af.png)

What can be created with PHP App Server are real, installable software applications that take a fraction of the time to create when compared to traditional desktop application development. Go from idea/concept to full deployment in 1/10th the time and support every major desktop OS with just one code base.

[![PHP App Server Overview and Tutorial video](https://user-images.githubusercontent.com/1432111/46376043-d3048080-c649-11e8-80e3-5729bf3b5e7e.png)](https://www.youtube.com/watch?v=pEykaINmvo0 "PHP App Server Overview and Tutorial")

PHP App Server is a fully-featured and extensible web server written in PHP with custom features specially designed to be used in a traditional desktop OS environment. When the user runs the software via their Start menu, application launcher, etc., the software starts the server and then launches the user's preferred web browser to access the application. PHP powers the backend while the web browser handles all the nitty-gritty details of displaying the user interface. The ready-made installer scripts simplify the process of creating final release packages for delivery to your user's computer systems.

[![Donate](https://cubiclesoft.com/res/donate-shield.png)](https://cubiclesoft.com/donate/) [![Discord](https://img.shields.io/discord/777282089980526602?label=chat&logo=discord)](https://cubiclesoft.com/product-support/github/)

Features
--------

* Fully extensible web server written in PHP. (This is not the built-in web server found in PHP itself.)
* Relies on the user's preferred web browser instead of a traditional GUI. Write applications with HTML, CSS, Javascript, and PHP.
* Long running process and high-performance localhost API support.
* WebSocket support.
* Virtual directory support.
* Dual document roots.
* Access and error logging.
* Zero configuration.
* Pre-made boilerplate installer scripts for Windows (EXE and MSI), Mac (.app bundle in a .tar.gz), and Linux (.tar.gz) with custom EULA and clean update support.
* Tiny installer sizes. From 85KB (Linux, .tar.gz) up to 10MB (Windows, .exe). When paying per GB of transfer, every byte counts.
* Branded to look like your own software application. Your app's custom icon is placed in the Start menu, application launcher, desktop/dock, etc.
* Optionally integrates with the open source, high-performance [CubicleSoft Software License Server](https://github.com/cubiclesoft/php-license-server). Quickly and easily enable license management in your software product.
* Fully isolated, zero conflict software.
* Compatible with [CubicleSoft software](https://github.com/cubiclesoft).
* Has a liberal open source license. MIT or LGPL, your choice.
* Designed for relatively painless integration into your project.
* Sits on GitHub for all of that pull request and issue tracker goodness to easily submit changes and ideas respectively.

Getting Started
---------------

Download or clone the latest software release. When cloning, be sure to use a fork and create a branch for your app before beginning development. Doing so avoids accidentally overwriting your software whenever you fetch upstream updates for PHP App Server itself.

For the rest of this guide, a recent version of PHP is assumed to be installed. There are many ways to make that happen.

From a command-line, run the following to get a list of command-line options:

```
php server.php -?
```

Start a web server on port 9002 by running:

```
php server.php -port=9002
```

The directory structure of PHP App Server is as follows:

* www - Where standard web server files go, including PHP files. This is actually treated as a view of two separate directories.
* support - Contains files required by `server.php` to operate.
* extensions - Where long running processes, high performance APIs, and WebSocket responders can be registered.
* installers - Pre-made installer scripts for various OSes.

Create an `index.php` file in the 'www' directory:

```php
array(
// "start" => time() + 5,
// "maxqueued" => 3
// ),
// "stdin" => false,
// "dir" => $_SERVER["PAS_ROOT"] . "/support/scripts/",
// "env" => ProcessHelper::GetCleanEnvironment(),
// "extraenv" => array("PASSWORD" => "supersecret"),
// "extra" => array(
// "title" => "Custom window title",
// "inputmode" => "readline"
// )
);

// Start the process.
require_once $rootpath . "/support/pas_run_process_sdk.php";

$rp = new PAS_RunProcessSDK();

$result = $rp->StartProcess("demo", $cmd, $options);
if (!$result["success"]) echo "An error occurred while starting a long-running process.";

echo "Done.";
?>
```

Each process is given a tag, which allows multiple running processes to be grouped by tag. In the example above, the tag is called "demo". The Javacsript SDK can later be used to show only processes that use a specific tag:

```php

// NOTE: Always put Javascript RunProcesSDK and TerminalManager class instances in a Javascript closure like this one to limit the XSRF attack surface.
(function() {
// Establish a new connection with a compatible WebSocket server.
var runproc = new RunProcessSDK('<?=PAS_RunProcessSDK::GetURL()?>', false, '<?=PAS_RunProcessSDK::GetAuthToken()?>');

// Debugging mode dumps incoming and outgoing packets to the web browser's debug console.
runproc.debug = true;

// Establish a new terminal manager instance.
var elem = document.getElementById('terminal-manager');

// Automatically attach to all channels with the 'demo' tag.
var options = {
tag: 'demo'
};

var tm = new TerminalManager(runproc, elem, options);
})();

```

The PHP SDK simplifies emitting the necessary CSS and Javscript dependencies into the HTML. The above code demonstrates setting up a WebSocket connection to the PHP App Server extension and connecting it to a TerminalManager instance to monitor for processes with the "demo" tag. TerminalManager is an included Javascript class that automatically creates and manages one or more ExecTerminals (also included) based on the input criteria. In this case, TerminalManager will automatically attach to any process created with a "demo" tag. An ExecTerminal looks like this:

![A screenshot of an ExecTerminal](https://user-images.githubusercontent.com/1432111/57195442-674c0400-6f07-11e9-9bcd-62269ca0de3c.png)

Each ExecTerminal wraps up a [XTerm.js Terminal](https://xtermjs.org/) instance with additional features:

* Status icons for process running/terminated, had output on stderr, and disconnected from WebSocket.
* Title that can be dynamically changed from the script via an ANSI escape sequence.
* Various buttons to attach and detach (not shown above), enter/exit fullscreen mode, forcefully terminate the process, and remove the ExecTerminal.
* Multiple input modes: 'interactive', 'interactive_echo' (rarely used), 'readline' (most common, keeps history), 'readline_secure', and 'none'.
* Intuitive terminal resizing.

And more.

Note that TerminalManager and ExecTerminal are not required for managing long-running processes but they do handle quite a few common scenarios. The example code above only scratches the surface of what can be done.

Here is the full list of TerminalManager options:

* fullscreen - A boolean indicating whether or not an attached ExecTerminal starts in fullscreen mode (Default is false).
* fullscreenbutton - A boolean indicating whether or not to display the button that allows the user to toggle fullscreen mode (Default is true).
* autoattach - A boolean indicating whether or not to automatically attach to channels and create ExecTerminals (Default is true).
* manualdetach - A boolean indicating whether or not to display the manual attach/detach button on the title bar (Default is false).
* terminatebutton - A boolean indicating whether or not to display the button that allows the user to forcefully terminate the process (Default is true).
* autoremove - A boolean indicating whether or not to automatically remove terminated ExecTerminals (Default is false). Can also be a string of 'keep_if_stderr', which removes the ExecTerminal if there was nothing output on stderr or keeps it if there was output on stderr.
* removebutton - A boolean indicating whether or not to display the button that allows the user to remove the ExecTerminal (Default is true).
* initviewportheight - A double containing the multiplier of the viewport height that ExecTerminals initialize at (Default is 0.5, which is 50% viewport height).
* historylines - An integer containing the number of lines of history to keep in 'readline' input mode (Default is 200). That user input box is fairly powerful.
* terminaltheme - A Javascript object containing a XTerm.js Terminal-compatible theme (Default is { foreground: '#BBBBBB', cursor: '#00D600', cursorAccent: '#F0F0F0' }).
* oncreate - An optional callback function that is called whenever an ExecTerminal is created (Default is null). The callback function must accept one parameter - callback(msg).
* onmessage - An optional callback function that is called whenever a message is received from the WebSocket for the TerminalManager instance (Default is null). The callback function must accept one parameter - callback(msg).
* channel - A boolean of false or an integer containing a specific channel to attach to (Default is false). Useful for monitoring exactly one running process.
* tag - A boolean of false or a string containing a tag name to watch for (Default is false). Useful for monitoring many running processes at one time.
* langmap - An object containing translation strings (Default is an empty object).

The included [XTerm PHP class](https://github.com/cubiclesoft/php-app-server/blob/master/support/xterm.php) offers seamless and simplified control over the output from a long-running script to the XTerm-compatible ExecTerminal in the browser. No need to remember ANSI escape codes. Here's an example script:

```php

```

Finally, if you use CubicleSoft [Admin Pack](https://github.com/cubiclesoft/admin-pack/) or [FlexForms](https://github.com/cubiclesoft/php-flexforms/) to build your application, the PHP SDK includes native FlexForms integration (i.e. no need to write Javascript/HTML):

```php
"Showing all long-running processes with the 'demo' tag.",
"fields" => array(
array(
"type" => "pas_run_process",
// "debug" => true,
"options" => array(
"tag" => "demo"
)
)
)
);

BB_GeneratePage("Process Demo", $menuopts, $contentopts);
?>
```

Creating Extensions
-------------------

Writing an extension requires a little bit of knowledge about how PHP App Server works: Extensions are loaded early on during startup so they can get involved in the startup sequence if they need to (mostly just for security-related extensions). Once the web server has started, every web request walks through the list of extensions and asks, "Can you handle this request?" If an extension responds in the affirmative (i.e. returns true), then the rest of the request is passed off to the extension to handle.

Since extensions are run directly inline with the core server, they get a significant performance boost and can do things such as respond over WebSocket or start long-running processes that would normally be killed off after 30 seconds by the normal PHP path.

However, those benefits come with two major drawbacks. The first is that if an extension raises an uncaught exception or otherwise crashes, it takes the whole web server with it. The second is that making code changes to an extension requires restarting the web server to test the changes, which can be a bit of a hassle. In general, the normal 'www' path is sufficient for most needs and extensions are for occasional segments of specialized logic.

The included [security token extension](extensions/1_security_token.php) is an excellent starting point for building an extension that can properly handle requests. The security token extension is fairly short, well-commented, and works.

The server assumes that the filename is a part of the class name. Whatever the PHP file is named, the class name within has to follow suit, otherwise PHP App Server will fail to load the extension. Extension names should start with a number, which indicates the expected order in which to call the extension.

The variables available to normal PHP scripts are also available to extensions via the global `$baseenv` variable (e.g. `$baseenv["DOCUMENT_ROOT_USER"]` and `$baseenv["PAS_USER_FILES"]`). Please do not alter the `$baseenv` values as that will negatively affect the rest of the application.

Always use the `ProcessHelper::StartProcess()` static function when starting external, long-running processes inside an extension. The [ProcessHelper](https://github.com/cubiclesoft/php-misc/blob/master/docs/process_helper.md) class is designed to start non-blocking processes in the background across all platforms. Note that the preferred way to start long-running processes is to use the long-running processes extension.

Server Termination
------------------

For certain tasks, it is important to tell PHP App Server to exit. For example, when upgrading a PHP App Server application on Windows, PHP itself needs to be updated and therefore can't be running during the upgrade. It's also generally good behavior to exit an application not too long after the last browser tab is closed.

There are two available methods for triggering early termination of the server:

* Use the Exit App extension. To use it, have the web browser establish a WebSocket connection to `/exit-app/` and send a valid `authtoken` and a `delay` in a JSON object that specifies the number of seconds to wait to terminate the server. Once used, every page of the app must connect to `/exit-app/` to keep the server alive. A minimum delay of 3 seconds is recommended. Web browsers tend to drop WebSocket connections as soon as they leave the page or the tab is closed.
* Send a `X-Exit-App` header in the response of a PHP script. The value of the header is the integer number of seconds to wait to terminate the server. A minimum delay of 3 seconds is recommended. This special header is not passed to the web browser but handled internally.

Example PHP code for the Exit App extension method:

```php

// NOTE: Always put WebSocket class instances in a Javascript closure like this one to limit the XSRF attack surface.
(function() {
function InitExitApp()
{
var ws = new WebSocket((window.location.protocol === 'https:' ? 'wss://' : 'ws://') + window.location.host + '/exit-app/');

ws.addEventListener('open', function(e) {
var msg = {
authtoken: '<?=hash_hmac("sha256", "/exit-app/", $_SERVER["PAS_SECRET"])?>',
delay: 3
};

ws.send(JSON.stringify(msg));
});

ws.addEventListener('close', function(e) {
setTimeout(InitExitApp, 500);
});
}

InitExitApp();
})();

```

Example PHP code for the header method:

```php

```

The extension is a more reliable method of detecting that all browser tabs to the application have been closed. However, if the application is unable to support the extension for some reason, then use the header method instead. The header method is best used on pages where it makes sense (e.g. a page with upgrade information).

Pre-Installer Tasks
-------------------

Before running the various scripts that generate installer packages, various files need to be created, renamed, and/or modified. Every file that starts with "yourapp" needs to be renamed to your application name, preferably restricted to all lowercase a-z and hyphens. This needs to be done so that updates to the software don't accidentally overwrite your work and so that any nosy users poking around the directory structure see the application's actual name instead of "yourapp".

* yourapp.png - A 512x512 pixel PNG image containing your application icon. It should be fairly easy to tell what the icon represents when shrunk to 24x24 pixels. The default icon works for testing but should be replaced with your own icon before deploying.
* yourapp.ico - This file can be deleted. It's only included to have a higher quality default ICO file. See the Windows EXE packaging instructions for more details.
* yourapp.phpapp - This file needs to be modified. More on this file in a moment.
* yourapp-license.txt - Replace the text in the file with an actual End User License Agreement (EULA) that's been written and approved by a real lawyer.

The 'yourapp.phpapp' file is a PHP file that performs the actual application startup sequence of starting the web server (server.php) and then launching the user's web browser. There is an `$options` array in the file that should be modified for your application's needs:

* business - A string containing your business or your name (Default is "Your Business or Name"). Shown under some OSes when displaying a program listing - notably Linux.
* appname - A boolean of false or a string containing your application's name (Default is "Your App"). When a boolean of false, the code attempts to automatically determine the app's name based on the directory it is installed in. This string is shown under some OSes when displaying a program listing - notably Linux.
* home - An optional string containing the directory to use as the "home" directory. Could be useful for implementing a "portable" version of the application.
* host - A string containing the IP address to bind to (Default is "127.0.0.1"). In general, don't change this.
* port - An integer containing the port number to bind to (Default is 0, which selects a random port number). In general, don't change this.
* quitdelay - An integer specifying the number of minutes after the last client disconnects to quit running the server (Default is 6). In general, don't change this.

The last three options are intended for highly specialized scenarios. Changing 'host' to something like "127.0.1.1" might be okay but don't use "0.0.0.0" or "::0", which binds the server publicly to the network interface. Binding to a specific 'port' number might seem like a good idea until users start complaining about error messages when they try to restart the application.

The 'quitdelay' option is interesting. The server portion of PHP App Server will stick around until 'quitdelay' minutes after the last client disconnects. The application should send a "heartbeat" request every five minutes to guarantee that the web server won't terminate itself before the user is finished using the application.

Installer Packaging
-------------------

Each platform packaging tool has its own instructions:

* [Windows, EXE](installers/win-innosetup/README.md) - Inno Setup based installer script with specialized support for the MSI build process.
* [Windows, MSI](installers/win-wix/README.md) - WiX Toolset based installer script. Build the EXE with Inno Setup first.
* [Mac OSX, .tar.gz](installers/osx-tar-gz/README.md) - Rearranges the application into an .app format and then wraps the entire application in a custom installer .app and finally puts the whole mess into a .tar.gz file. Think Very Different. This approach also doesn't require owning a Mac, which is kind of cool because not everyone can afford the expensive hardware.
* [Linux, .tar.gz](installers/nix-tar-gz/README.md) - Produces the smallest output file out of all of the application packagers. The installer relies on the system package manager to install PHP and other dependencies on Debian, RedHat, and Arch-based systems - that is, there is fairly broad distro coverage. The installer itself requires a Freedesktop.org-compliant window manager that supports `xdg-utils` (Gnome, KDE, XFCE, etc. are all fine).

There are some known packaging issues:

* Code signing support is missing - I don't like code signing and neither should anyone else until we can all use DNSSEC DANE TLSA Certificate usage 3 with Authenticode/Gatekeeper/etc. (Hint to Microsoft/Apple: Publisher = domain name). Feel free to open a pull request if you implement really good support for optional code signing in the various packagers. I'm not particularly interested in code signing given how pointless, fairly expensive, and obnoxious it tends to be. Note that on Mac OSX, a user has to hold Ctrl while clicking, then select Open from the menu, and finally open the app when Gatekeeper complains about the lack of a digital signature.
* The Mac OSX installer has a continually bouncing icon while installing the software. Since the installer relies on the user's web browser, clicking the icon does nothing.

The installers and the server software have some interesting tales behind them. Maybe I'll share those stories one day. For now, enjoy building your next application in PHP App Server!