为Windows,Mac和Linux桌面操作系统创建用HTML,CSS,JavaScript和PHP编写的轻巧,可安装的应用程序。

与传统桌面应用程序开发相比,使用PHP应用程序服务器可以创建的是真实的可安装软件应用程序。从想法/概念到1/10的全部部署,并仅使用一个代码库来支持每个主要的桌面操作系统。

PHP App Server是用PHP编写的完整且可扩展的Web服务器,其自定义功能专门设计用于传统的台式OS环境中。当用户通过其开始菜单,应用程序启动器等运行软件时,软件启动服务器,然后启动用户首选的Web浏览器以访问应用程序。当Web浏览器处理显示用户界面的所有细节细节时,PHP会为后端提供动力。现成的安装程序脚本简化了创建最终版本软件包的过程,以交付给用户的计算机系统。
下载或克隆最新的软件版本。克隆时,请确保在开始开发之前为您的应用程序创建分支。这样做会避免在PHP App Server本身获取上游更新时不小心覆盖软件。
对于本指南的其余部分,假定已安装了最新版本的PHP。有很多方法可以实现这一目标。
从命令行中,运行以下以获取命令行选项列表:
php server.php -?
通过运行:在端口9002上启动Web服务器:
php server.php -port=9002
PHP应用程序服务器的目录结构如下:
server.php需要操作的文件。在“ www”目录中创建一个index.php文件:
<?php
phpinfo ();使用您的Web浏览器连接到运行服务器:
http://127.0.0.1:9002/
phpinfo()的输出显示在浏览器中,并将请求的结果写入命令行。
将URL更改为:
http://127.0.0.1:9002/api/v1/account/info
相同的index.php文件运行。
重命名或将index.php文件复制到api.php并重新加载页面。现在称为api.php 。 PHP App Server的虚拟目录功能在开发应用程序时可能会很有用。
安装的软件应用程序无法写入“ www”。应用程序通常由系统上的特权用户安装,但是运行该软件的人员通常没有足够的权限写入“ www”目录。这是在使用PHP App Server开发软件应用程序时要牢记的重要考虑因素。幸运的是,服务器中已经内置的此问题有一个解决方案:双文档根。
当PHP代码从应用程序的“ www”目录执行时,它可以访问五个$_SERVER变量,这些变量通过PHP App Server环境传递,并且是唯一的:
DOCUMENT_ROOT_USER的父目录。也可以写入但不能由URL引用。对于为应用程序存储私有数据(例如SQLite数据库)有用。server.php居住)。对于support子目录中的文件很有用。当向Web服务器提出请求时,PHP App Server首先在应用程序的“ www”目录中查找文件。如果它在那里找不到文件,则将检查DOCUMENT_ROOT_USER指定的路径中的文件。
编写依赖Web浏览器的LocalHost服务器应用程序可能会导致严重的系统安全性违规行为,从数据控制丢失到损害用户的文件系统。只要正确编写了应用程序,Web浏览器的策略通常将保护用户免受尝试访问PHP App Server控制内容的恶意网站和用户。
但是,这里有一些重要的,请选择所有基于PHP App Server的软件应用程序必须积极抗辩(按重要性顺序):
$_SERVER["PAS_USER_FILES"]或用户定义的位置存储敏感用户数据,而不是$_SERVER["DOCUMENT_ROOT_USER"] 。始终询问用户如果他们认为某些东西要敏感该怎么办(例如,询问与向用户显示复选框一样简单)。以隐私为中心的人通常会说出自己的想法。$_SERVER["PAS_SECRET"]与CubicleSoft Admin Pack或其他应用程序框架相结合,有助于解决此问题。服务器扩展程序通常还需要基于$_SERVER["PAS_SECRET"]身份验证令牌。OWASP排名前10名列表中还有许多其他安全考虑因素,并且还要记住OWASP攻击列表,但这是很大的。
PHP App Server包含功能强大的服务器扩展名和两个SDK,可让启动,管理和监视长期运行的过程轻松且可从PHP和JavaScript中安全。启动流程作为用户运行,PHP App Server正在运行AS,但不受常规CGI/FASTCGI请求(例如常规CGI/FASTCGI请求)的限制。可以通过随附的JavaScript SDK从Web浏览器中积极监控运行过程,甚至可以与之进行交互。

理想情况下,长期运行的脚本应存储在主PHP App Server“支持”目录中的“脚本”子目录中。这样,它们远离了主要的Web根部,但是该应用程序仍然可以通过$_SERVER["PAS_ROOT"]找到它们。
这是使用PHP SDK启动名为“ Test.php”的PHP脚本的示例:
<?php
$ rootpath = str_replace ( "\" , " / " , dirname ( __FILE__ ));
// Load the PHP App Server common functions.
require_once $ _SERVER [ " PAS_ROOT " ] . " /support/process_helper.php " ;
require_once $ _SERVER [ " PAS_ROOT " ] . " /support/pas_functions.php " ;
$ cmd = escapeshellarg ( PAS_GetPHPBinary ());
$ cmd .= " " . escapeshellarg ( realpath ( $ _SERVER [ " PAS_ROOT " ] . " /support/scripts/test.php " ));
$ options = array (
// "rules" => 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. " ;
?>每个过程都会给出一个标签,该标签允许通过标签对多个运行过程进行分组。在上面的示例中,标签称为“演示”。 Javacsript SDK稍后可用于显示使用特定标签的过程:
<?php
header ( " Content-Type: text/html; charset=UTF8 " );
?>
<!DOCTYPE html>
<html>
<body>
<?php
$ rootpath = str_replace ( "\" , " / " , dirname ( __FILE__ ));
require_once $ rootpath . " /support/pas_run_process_sdk.php " ;
PAS_RunProcessSDK:: OutputCSS ();
PAS_RunProcessSDK:: OutputJS ();
?>
<div id="terminal-manager"></div>
<script type="text/javascript">
// 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);
})();
</script>
</body>
</html>PHP SDK简化了将必要的CSS和Javscript依赖项排放到HTML中。上面的代码演示了设置与PHP App Server扩展名的Websocket连接并将其连接到终端Manager实例,以监视使用“演示”标签的进程。 TerminalManager是一个包含的JavaScript类,它会根据输入标准自动创建和管理一个或多个execterminal(也包括)。在这种情况下,TerminalManager将自动连接到使用“演示”标签创建的任何过程。外观看起来像这样:

每个execterminal都包含一个具有其他功能的Xterm.js终端实例:
还有更多。
请注意,管理长期运行的过程并不需要终端管理器和外观,但它们确实处理了很多常见的情况。上面的示例代码仅刮擦可以完成的操作的表面。
以下是终端man选项的完整列表:
随附的XTERM PHP类提供了从长期运行脚本到浏览器中与XTerm兼容的execterminal的输出的无缝和简化控制。无需记住ANSI逃生代码。这是一个示例脚本:
<?php
if (! isset ( $ _SERVER [ " argc " ]) || ! $ _SERVER [ " argc " ])
{
echo " This file is intended to be run from the command-line. " ;
exit ();
}
$ rootpath = str_replace ( "\" , " / " , dirname ( __FILE__ ));
require_once $ rootpath . " /../xterm.php " ;
for ( $ x = 0 ; $ x < 5 ; $ x ++)
{
echo " Test: " . ( $ x + 1 ) . "n" ;
sleep ( 1 );
}
echo " That's boring. Let's... " ;
sleep ( 1 );
XTerm:: SetItalic ();
echo " spice it up! n" ;
XTerm:: SetItalic ( false );
sleep ( 1 );
$ palette = XTerm:: GetBlackOptimizedColorPalette ();
for ( $ x = 5 ; $ x < 10 ; $ x ++)
{
$ num = mt_rand ( 17 , 231 );
XTerm:: SetForegroundColor ( $ palette [ $ num ]);
echo " Test: " . ( $ x + 1 ) . " (Color " . $ palette [ $ num ] . " ) n" ;
sleep ( 1 );
}
XTerm:: SetForegroundColor ( false );
XTerm:: SetTitle ( " Changing the title... " );
usleep ( 250000 );
XTerm:: SetTitle ( " Changing the title...like " );
usleep ( 250000 );
XTerm:: SetTitle ( " Changing the title...like a " );
usleep ( 250000 );
XTerm:: SetTitle ( " Changing the title...like a BOSS! " );
usleep ( 500000 );
echo "n" ;
echo " Enter some text: " ;
$ line = rtrim ( fgets ( STDIN ));
XTerm:: SetBold ();
echo " Here's what you wrote: " . $ line . "nn" ;
XTerm:: SetBold ( false );
echo " [Switching to 'readline_secure' mode] nn" ;
XTerm:: SetCustomInputMode ( ' readline_secure ' );
echo " Enter some more text: " ;
XTerm:: SetColors ( 0 , 0 );
$ line = rtrim ( fgets ( STDIN ));
XTerm:: SetColors ( false , false );
XTerm:: SetCustomInputMode ( ' readline ' );
XTerm:: SetBold ();
echo " Here's what you wrote: " . $ line . "nn" ;
XTerm:: SetBold ( false );
echo " Done. n" ;
?>最后,如果您使用CubicleSoft Admin Pack或FlexForms来构建应用程序,则PHP SDK包含本地FlexForms Integration(即无需编写JavaScript/HTML):
<?php
// Admin Pack and FlexForms integration.
require_once " support/pas_run_process_sdk.php " ;
$ contentopts = array (
" desc " => " 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 );
?> 编写扩展名需要一些有关PHP应用程序服务器的工作方式的知识:扩展程序在启动期间的早期加载,以便如果需要的话,他们可以参与启动序列(主要仅用于安全相关的扩展)。 Web服务器启动后,每个Web请求都会浏览扩展名单,并问:“您可以处理此请求吗?”如果扩展名在肯定的(即返回true)中响应,则其余的请求将传递给扩展名处理。
由于扩展程序是直接与Core Server直接运行的,因此它们会得到显着的性能提升,并且可以做一些事情,例如通过Websocket做出响应或开始长期运行的过程,这些过程通常在30秒后被普通的PHP路径杀死。
但是,这些好处带有两个主要缺点。首先是,如果扩展名提出了未被发现的异常或以其他方式崩溃,则将整个Web服务器带有它。第二是,对扩展程序进行代码更改需要重新启动Web服务器以测试更改,这可能有些麻烦。通常,正常的“ www”路径足以满足大多数需求,并且扩展是偶尔的专业逻辑段。
随附的安全令牌扩展名是构建可以正确处理请求的扩展程序的绝佳起点。安全令牌扩展名相当简短,注重且作品。
服务器假设文件名是类名称的一部分。无论命名PHP文件如何,都必须效仿的类名称,否则PHP App Server将无法加载扩展名。扩展名称应以一个数字开头,这表示调用扩展名的预期顺序。
普通PHP脚本可用的变量也可以通过全局$baseenv变量(例如$baseenv["DOCUMENT_ROOT_USER"]和$baseenv["PAS_USER_FILES"] )。请不要更改$baseenv值,因为这会对应用程序的其余部分产生负面影响。
始终在启动外部,长期运行的进程时,请始终使用ProcessHelper::StartProcess()静态函数。 ProcessHelper类旨在在所有平台的背景中启动非阻滞过程。请注意,开始长期运行过程的首选方法是使用长期运行的过程扩展。
对于某些任务,重要的是要告诉PHP App Server退出。例如,在Windows上升级PHP App Server应用程序时,需要更新PHP本身,因此在升级过程中不能运行。在最后一个浏览器选项卡关闭后不久,退出应用程序也不久。
有两种可用的方法来触发服务器的早期终止:
/exit-app/ ,并在JSON对象中发送有效的authtoken和一个delay ,该对象指定了等待终止服务器的秒数。使用后,应用程序的每个页面都必须连接到/exit-app/以保持服务器的生命。建议最小延迟3秒。 Web浏览器离开页面或关闭选项卡后往往会删除Websocket连接。X-Exit-App标头。标题的值是等待终止服务器的秒数的整数数。建议最小延迟3秒。这个特殊的标题未传递给网络浏览器,而是内部处理的。出口应用扩展方法的示例PHP代码:
<script type="text/javascript">
// 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 ();
})();
</script>标题方法的示例PHP代码:
<?php
// User clicked an "Exit application" link or something.
header ( " X-Exit-App: 3 " );
?>扩展程序是一种更可靠的方法,可以检测到应用程序的所有浏览器选项卡已关闭。但是,如果该应用程序由于某种原因无法支持扩展程序,请改用标头方法。标题方法最好在有意义的页面上使用(例如带有升级信息的页面)。
在运行生成安装程序软件包的各种脚本之前,需要创建,重命名和/或修改各种文件。每个以“ yourapp”开头的文件都需要重命名为您的应用程序名称,最好仅限于所有小写AZ和连字符。这需要做到这一点,以便对软件的更新不会意外覆盖您的工作,以便任何围绕目录结构戳戳的用户都可以看到应用程序的实际名称,而不是“ yourapp”。
“ yourapp.phpapp”文件是一个PHP文件,可以执行启动Web Server(Server.php)的实际应用程序启动序列,然后启动用户的Web浏览器。文件中有一个$options数组,应根据您的应用程序的需求进行修改:
最后三个选项旨在用于高度专业化的方案。将“主机”更改为“ 127.0.1.1”之类的东西可能还可以,但不要使用“ 0.0.0.0”或“ :: 0”,该服务器将服务器公开绑定到网络接口。在用户试图重新启动应用程序时,在用户开始抱怨错误消息之前,与特定的“端口”数字绑定似乎是个好主意。
“ Quitdelay”选项很有趣。 PHP应用程序服务器的服务器部分将一直持续到最后一个客户端断开连接后的“ Quitdelay”。该应用程序应每五分钟发送一次“心跳”请求,以确保在用户使用应用程序完成用户之前不会终止自身。
每个平台包装工具都有自己的说明:
xdg-utils (GNOME,KDE,XFCE等)的Freedesktop.org兼容窗口管理器。有一些已知的包装问题:
安装程序和服务器软件背后有一些有趣的故事。也许有一天我会分享这些故事。现在,请在PHP App Server中构建下一个应用程序!