You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 07:47:22