PHP实现动态响应码:生成eSCL/AirScan双阶段201响应
如何在PHP中实现eSCL/AirScan风格的分阶段201 Created响应
首先得明确一个核心问题:你的代码之所以导致客户端崩溃,是因为HTTP协议是严格的请求-响应模型——单个客户端请求只能对应一个完整的HTTP响应,你不能在已经发送响应头和部分内容后,再次发送新的响应头。重复调用header()函数会破坏响应结构,客户端无法解析这种不符合规范的内容,自然会出错。
针对eSCL/AirScan的场景,这类协议的常规交互逻辑是:客户端发起扫描请求后,服务器返回201 Created响应(包含初始扫描状态和扫描资源的URL),之后客户端会通过这个资源URL轮询状态,直到服务器返回ReadyToUpload状态。
下面给你两种可行的实现方案:
方案一:使用服务器发送事件(SSE)主动推送状态
如果需要服务器主动向客户端推送状态更新,SSE是最适合的方案,它允许服务器在一个请求连接内向客户端多次发送数据:
<?php // 设置SSE响应头,确保客户端保持连接 header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('Connection: keep-alive'); // 返回201状态码,符合eSCL要求 header("HTTP/1.1 201 Created"); $Name = "scan_".uniqid(); $ext = "png"; // 发送初始状态的XML(符合eSCL规范的结构) $initialXml = <<<XML <scan:ScanJobStatus xmlns:scan="http://schemas.hp.com/imaging/escl/2011/05/03"> <scan:PageState>PreparingScan</scan:PageState> <scan:BinaryURL>/eSCL/Scans/$Name.$ext</scan:BinaryURL> </scan:ScanJobStatus> XML; echo "data: $initialXml\n\n"; ob_flush(); flush(); // 模拟耗时的扫描处理(替换成你的实际业务代码) sleep(5); // 发送更新后的状态XML $updatedXml = <<<XML <scan:ScanJobStatus xmlns:scan="http://schemas.hp.com/imaging/escl/2011/05/03"> <scan:PageState>ReadyToUpload</scan:PageState> <scan:BinaryURL>/eSCL/Scans/$Name.$ext</scan:BinaryURL> </scan:ScanJobStatus> XML; echo "data: $updatedXml\n\n"; ob_flush(); flush(); // 发送关闭信号,结束连接 echo "event: close\ndata: done\n\n"; ob_flush(); flush(); ?>
方案二:客户端轮询实现状态更新(更贴合eSCL协议规范)
eSCL协议通常采用客户端轮询的方式,服务器维护扫描任务的状态,客户端定期请求状态接口:
1. 初始扫描请求处理脚本(创建任务并返回初始状态)
<?php session_start(); $Name = "scan_".uniqid(); $ext = "png"; // 存储扫描任务状态到session(生产环境建议用Redis/数据库存储,避免并发问题) $_SESSION['scan_status'] = [ 'state' => 'PreparingScan', 'binary_url' => "/eSCL/Scans/$Name.$ext" ]; // 返回201 Created响应,包含规范的XML内容 header("HTTP/1.1 201 Created"); header('Content-Type: application/xml'); echo <<<XML <scan:ScanJobStatus xmlns:scan="http://schemas.hp.com/imaging/escl/2011/05/03"> <scan:PageState>{$_SESSION['scan_status']['state']}</scan:PageState> <scan:BinaryURL>{$_SESSION['scan_status']['binary_url']}</scan:BinaryURL> </scan:ScanJobStatus> XML; // 异步启动扫描处理任务(避免阻塞初始响应返回) // 生产环境建议用消息队列,这里用exec做简单示例 exec("php /path/to/scan_process.php $Name $ext > /dev/null 2>&1 &"); ?>
2. 扫描处理脚本(scan_process.php)
<?php session_start(); $Name = $argv[1]; $ext = $argv[2]; // 模拟耗时的扫描操作 sleep(5); // 更新扫描任务状态 $_SESSION['scan_status']['state'] = 'ReadyToUpload'; ?>
3. 状态轮询接口(供客户端定期请求)
<?php session_start(); header('Content-Type: application/xml'); // 若状态不存在,返回对应错误 if (!isset($_SESSION['scan_status'])) { header("HTTP/1.1 404 Not Found"); exit; } // 返回当前扫描状态的XML echo <<<XML <scan:ScanJobStatus xmlns:scan="http://schemas.hp.com/imaging/escl/2011/05/03"> <scan:PageState>{$_SESSION['scan_status']['state']}</scan:PageState> <scan:BinaryURL>{$_SESSION['scan_status']['binary_url']}</scan:BinaryURL> </scan:ScanJobStatus> XML; ?>
关键注意事项
- 严格遵循eSCL XML规范:必须包含正确的命名空间,标签结构要和官方定义一致,否则客户端可能无法识别状态。
- 生产环境状态存储:不要依赖session,建议用Redis或数据库存储扫描状态,避免多请求并发时的状态覆盖问题。
- 异步处理核心任务:扫描操作耗时较长时,一定要异步执行,不能阻塞初始响应的返回,否则客户端会超时。
内容的提问来源于stack exchange,提问作者user178167
相关产品推荐
相关产品推荐

