21CTO导读:PHP8.6新增一个SAPI,可以控制打印机,打印或其他相关操作。
PHP 新增了一个 SAPI,专门用于控制打印机。我们可以使用它进行配置,--enable-cups,然后就可以lp my-script.php。
这不是玩笑。好吧,它确实也算是个玩笑,但它也能说得通。
sapi/cups--enable-cups 这是一个新的 SAPI 模块,你可以在配置时启用它。
它会生成一个名为 `php-cups文件实现了 CUPS 过滤器接口——自 20 世纪 90 年代以来,所有 CUPS 过滤器都使用相同的参数 argv 约定:
php-cups job-id user title copies options调用 SAPI 时,它会从头开始启动 PHP(MINIT、RINIT、加载所有扩展、解析 php.ini),填充$_SERVER所有 CUPS 作业元数据,然后执行你的脚本。
你输出的任何内容,使用echo都会打印到标准输出 (stdout)。通过辅助函数发出的任何内容都会作为 CUPS 调度程序协议输出到标准错误输出中 (stderr)。共有十二个函数,全部带有前缀cups_,全部向标准错误输出写入单行协议消息:
cups_state('+processing'); // STATE: +processingcups_state('-toner-low'); // STATE: -toner-lowcups_page(1, 1); // PAGE: 1 1cups_total_pages(42); // PAGE: total 42cups_error('out of paper'); // ERROR: out of papercups_warning('low toner'); // WARNING: low tonercups_info('job started'); // INFO: job startedcups_attr('marker', '42'); // ATTR: marker=42// ... plus debug, emerg, alert, crit, notice
清理程序会在写入之前将空格替换\n\r空格——消息字符串中的单个换行符允许你注入任意 CUPS 协议命令。
我有一台Brother QL-820NWB USB标签打印机。
CUPS队列已经配置好,标签卷纸也已装入,聪明的人到此为止。我编写了一个PHP脚本,用于生成62mm × 100mm标签的PostScript代码。如下:
cups_state('+processing');cups_info('Generating label via PHP CUPS SAPI');echo "%!PS-Adobe-3.0\n";echo "%%BoundingBox: 0 0 175.68 282.96\n";echo "/Helvetica-Bold findfont 18 scalefont setfont\n";echo "20 242 moveto (PHP CUPS SAPI LABEL) show\n";echo "/Helvetica findfont 12 scalefont setfont\n";echo "20 212 moveto (Brother QL-820NWB) show\n";echo "/Courier findfont 10 scalefont setfont\n";echo "20 180 moveto (Generated: " . date('Y-m-d H:i:s') . ") show\n";echo "20 160 moveto (SAPI: " . php_sapi_name() . ") show\n";echo "showpage\n";cups_page(1, 1);cups_total_pages(1);cups_state('-processing');
打印管道如下:
php-cups label.php → PostScript on stdout (962 bytes)→ ps2pdf → PDF (4746 bytes)→ lp -d Brother_QL_820NWB→ cgpdftoraster → rastertobrotherQL800→ thermal print head → sticker
CUPS 在作业期间读取的 stderr 流:
STATE: +processingINFO: Generating label via PHP CUPS SAPIPAGE: 1 1PAGE: total 1STATE: -processingINFO: Label generated successfully
CUPS 过滤器从标准输入 (stdin) 读取打印数据,将转换后的数据写入标准输出 (stdout),并将状态消息输出到标准错误 (stderr)——这就是它的全部接口。
PHP SAPI 模块是一个函数指针结构体,它决定了 PHP 如何读取输入、写入输出、处理错误以及填充数据$_SERVER。这两个抽象概念之间的映射关系非常精确,这表明它们是专门为此而设计的。
SAPI 结构体位于…… sapi/cups/php_cups.c。关键钩子如下:
ub_write指向write(STDOUT_FILENO, ...)一个带有部分写入重试循环的函数。PHP 的输出缓冲会在每次刷新时调用此函数echo。print脚本生成的每个 PostScript 或 ESC/P 字节都会经过此函数,然后才会到达 CUPS 链中的下一个过滤器。
log_message写入到 stderr fprintf(stderr, "DEBUG: %s\n", ...)。如果error_log未设置,PHP 的内部错误会输出到此处,并自动添加 CUPS 调度器的前缀。十二个cups_*()辅助函数会绕过此步骤——它们直接使用各自的协议前缀(STATE、PAGE、ERROR、ATTR 等)写入 stderr。
register_server_variables调用php_register_variable()CUPS 提供的每个字段:(来自CUPS_JOB_ID )argv[1],CUPS_USER(argv[2]),CUPS_TITLE()argv[3],CUPS_COPIES(argv[4]),CUPS_OPTIONS () argv[5],CUPS_FILENAME(argv[6]),以及从 CUPS 为每个过滤过程设置的环境变量中提取的更多字段。
CUPS_PRINTERCUPS_PPDCUPS_CONTENT_TYPECUPS_FINAL_CONTENT_TYPECUPS_DEVICE_URI
header_handler,,send_headers都是send_header存根。打印机内部没有 HTTP 标头。header_handler返回 0,send_headers返回空值SAPI_HEADER_SENT_SUCCESSFULLY,send_header是一个空函数。
read_cookies返回结果为NULL:打印机没有 cookie。打印机不接收 POST 请求。这些事实无需赘述。
read_post 入口点main()位于 [sapi/cups/cups_filter.c]。它解析 CUPS 参数,将其注册SIGPIPE为忽略项(上游过滤器的管道破裂不应导致进程在页面加载过程中终止),并设置一个volatile sig_atomic_t标志 SIGTERM,以便 PHP 脚本可以通过 [此处应填写关闭标志名称] 实现优雅关闭pcntl_signal()。
然后,它检查输入文件或标准输入是否包含[此处应填写关闭标志名称]——如果包含,则启动 PHP 并执行脚本。如果不包含,则向标准错误输出写入错误并退出。
PHP引导程序与嵌入式SAPI完全相同——按顺序调用7次:
sapi_startup(&php_cups_module); // SINITphp_cups_module.startup(&php_cups_module); // MINIT — loads extensions, parses php.iniphp_request_startup(); // RINIT — registers $_SERVER, module RINIT hooksphp_execute_script(&file_handle); // Execute the PHP scriptphp_request_shutdown(NULL); // RSHUTDOWNphp_module_shutdown(); // MSHUTDOWNsapi_shutdown(); // SSHUTDOWN
硬编码的 INI 文件设置禁用了任何干扰打印管道的操作:html_errors=0(stderr 由 CUPS 解析,而不是由浏览器解析),implicit_flush=1(output_buffering=0打印数据应该立即流式传输,而不是缓冲), max_execution_time=0(max_input_time=-1一个 2000 页的打印作业不应该因为 PHP 的监视程序触发而终止)。
构建系统集成包含三个文件:config0.m4声明文件--enable-cups、连接 平台特定链接器命令的文件以及config.m4提供目标文件。autoconf机制负责其余部分——将 cups 添加到文件、生成文件以及将构建规则插入生成的 Makefile 中。PHP_SELECT_SAPIMakefile.fragcupsinstall-cupsPHP_BINARIESPHP_CUPS_OBJS
整个程序由两个源文件组成,共 700 行 C 代码。
https://github.com/HelgeSverre/php-src,分支为cups-sapi。配置方式为 --enable-cups。
这里面的RFC 文档内容更加详尽——包括完整的 stderr 协议表、从设计到最终文档的详细步骤lp、安全模型和协议注入预防机制、基准测试(25 毫秒启动时间,20MB RSS 文件大小),以及每种受支持的 MIME 类型的过滤链机制。
非常好用方便的新功能,PHP可以控制打印机,有好多想法又可以实现了。我现在还在测试,希望对各位开发者有帮助。
作者:莫非
本篇文章为 @ 场长 创作并授权 21CTO 发布,未经许可,请勿转载。
内容授权事宜请您联系 webmaster@21cto.com或关注 21CTO 微信公众号。
该文观点仅代表作者本人,21CTO 平台仅提供信息存储空间服务。
请扫描二维码,使用微信支付哦。